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
volatile
write:- Flushes data from CPU cache to main memory
- On a
volatile
read:- 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
volatile
for 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.