1. Introduction to volatile
1.1. Definition
The volatile keyword in Java is used to mark a variable as being shared among threads. It ensures that all reads and writes to that variable go directly to and from main memory (RAM), not CPU cache.
1.2. Key Properties
- Guarantees visibility of changes across threads
- Prevents the JVM and CPU from reordering reads/writes (memory barrier)
- Does not provide atomicity for compound operations (like
count++)
1.3. Syntax
public class Counter {
private volatile int count = 0;
public void increment() {
count++; // NOT ATOMIC
}
}
Although count is volatile, count++ is not atomic (it involves read-modify-write).
2. Introduction to AtomicReference
2.1. Definition
AtomicReference is a class in the java.util.concurrent.atomic package that provides atomic operations on object references.
It allows you to:
- Read the current value atomically
- Set a new value atomically
- Perform Compare-And-Set (CAS) operations
2.2. Key Properties
- Provides atomicity (read-modify-write is thread-safe)
- Ensures visibility like
volatile - Internally uses low-level CPU atomic instructions
2.3. Syntax
import java.util.concurrent.atomic.AtomicReference;
public class SharedData {
private AtomicReference<String> name = new AtomicReference<>("Sam");
public void updateName(String newName) {
name.set(newName); // Atomic and visible
}
public void updateIfEqual(String expected, String newName) {
name.compareAndSet(expected, newName); // Atomic CAS operation
}
public String getName() {
return name.get(); // Atomic read
}
}
3. Feature Comparison Table
| Feature | volatile | AtomicReference |
|---|---|---|
| Type | Keyword (language-level) | Class (API-level abstraction) |
| Thread Safety | Guarantees visibility only | Guarantees atomicity and visibility |
| Atomic Updates | ❌ Not supported | ✅ Supported via compareAndSet, getAndSet, etc. |
| Compound Actions | ❌ Not thread-safe (e.g., x++) | ✅ Safe for read-modify-write |
| Use Case | Primitive flags, status indicators | Object references, shared mutable state |
| Lock-free | Yes | Yes (uses CAS) |
| Memory Semantics | Ensures visibility + ordering | Ensures atomicity + visibility |
| Java Version | Since Java 1.2 | Since Java 1.5 |
4. Internal Working
4.1. How volatile Works Internally
- Ensures happens-before relationship between reads and writes
- On a
volatilewrite:- Flushes data from CPU cache to main memory
- On a
volatileread:- Reads the most recent value from memory
- Uses memory barriers/fences
4.2. How AtomicReference Works Internally
- Internally uses Compare-And-Swap (CAS) operation
- CAS is a low-level atomic CPU instruction:
if current_value == expected_value then set to new_value
- CAS avoids locks and ensures atomicity without blocking
5. Code Examples
5.1. Problem with volatile (No Atomicity)
public class VolatileCounter {
private volatile int count = 0;
public void increment() {
count++; // Not atomic: read-modify-write
}
public int getCount() {
return count;
}
}
If multiple threads call increment(), the value of count may be incorrect due to race conditions.
5.2. Solution Using AtomicReference
import java.util.concurrent.atomic.AtomicReference;
public class AtomicCounter {
private AtomicReference<Integer> count = new AtomicReference<>(0);
public void increment() {
Integer oldValue, newValue;
do {
oldValue = count.get();
newValue = oldValue + 1;
} while (!count.compareAndSet(oldValue, newValue)); // Retry if CAS fails
}
public int getCount() {
return count.get();
}
}
Here, compareAndSet ensures thread-safe atomic update using CAS.
6. When to Use volatile
Use volatile when:
- You need a flag or status indicator
- You don’t need read-modify-write operations
- Example:
volatile boolean isRunning = true;
Common use cases
- Double-checked locking
- One-time status variables (e.g., shutdown flags)
- State transitions in simple protocols
7. When to Use AtomicReference
Use AtomicReference when:
- You need to atomically update a reference
- You need CAS-based updates
- You want to build non-blocking data structures
Common use cases
- Atomic updates to shared objects
- Building lock-free queues, stacks
- Implementing thread-safe caching
- Concurrent algorithms (e.g., ABA-safe algorithms)
8. Limitations
8.1. Limitations of volatile
- Cannot ensure atomicity for compound operations
- Doesn’t help with thread-safe updates to collections or objects
- Cannot block or wait on a volatile value
8.2. Limitations of AtomicReference
- More verbose and complex for simple flags
- CAS-based loops can cause livelock if contention is high
- May be slower than
volatilefor simple read/write-only scenarios
Advanced Tip: AtomicReference with Custom Objects
You can use AtomicReference to safely manage concurrent updates to custom objects:
class Person {
final String name;
final int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
}
AtomicReference<Person> personRef = new AtomicReference<>(new Person("Sam", 30));
Person current = personRef.get();
Person updated = new Person("Sam", 31);
personRef.compareAndSet(current, updated);
This is immutable object replacement, which avoids complex locking.
