Learnitweb

Difference Between volatile and AtomicReference in Java

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

FeaturevolatileAtomicReference
TypeKeyword (language-level)Class (API-level abstraction)
Thread SafetyGuarantees visibility onlyGuarantees 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 CasePrimitive flags, status indicatorsObject references, shared mutable state
Lock-freeYesYes (uses CAS)
Memory SemanticsEnsures visibility + orderingEnsures atomicity + visibility
Java VersionSince Java 1.2Since 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.