Back to volatile
Q4Question 4 of 5

volatile vs Atomic classes?

Key Concepts
  • volatile provides visibility only: use for flags, status variables, and single-writer patterns.
  • Atomic classes provide visibility plus atomicity via CAS: use for counters, conditional updates, and reference swaps.
  • AtomicInteger and AtomicLong handle numeric read-modify-write operations atomically.
  • AtomicReference handles atomic reference swaps with compareAndSet for immutable object publishing.
  • LongAdder is the fastest choice for high-contention counters (per-thread striped cells).
  • For multi-variable invariants or complex compound operations, neither volatile nor Atomic is sufficient: use synchronized.

Definition

volatile provides visibility only (for flags and status), while Atomic classes provide visibility plus atomicity via hardware CAS (for counters and conditional updates).
  • volatile – guarantees that every read fetches from main memory and every write flushes to main memory. Sufficient for simple read/write patterns where the new value does not depend on the old value.
  • Atomic classes – provide both visibility and atomicity using hardware CAS (Compare-And-Swap). incrementAndGet(), compareAndSet(), and updateAndGet() are all atomic compound operations.
  • Atomic family – AtomicInteger, AtomicLong, AtomicBoolean, AtomicReference, AtomicIntegerArray, LongAdder (striped for high contention), AtomicStampedReference (solves ABA problem).
  • Neither is enough – for multi-variable invariants or complex compound operations involving multiple fields, use synchronized or ReentrantLock.

volatile is a glass display case showing the current gold price. Everyone can see the latest price (visibility), but if two traders try to update the price simultaneously, one update is lost. Atomic classes are a mechanical price ticker that clicks to the next value. Each click is one atomic hardware operation. Two traders clicking simultaneously: both clicks register.

Understanding the Concepts

When should you use volatile?

JAVA
package com.bank.core;

import java.math.BigDecimal;

public class VolatileUseCases {
    // Shutdown flag: one writer, many readers
    private volatile boolean running = true;

    // Latest price: one writer publishes, many readers consume
    private volatile BigDecimal latestPrice = BigDecimal.ZERO;

    // Configuration swap: replace entire immutable config
    private volatile AppConfig config = AppConfig.defaults();

    public void stop() { running = false; }
    public boolean isRunning() { return running; }
}

record AppConfig(String dbUrl, int poolSize) {
    static AppConfig defaults() { return new AppConfig("jdbc:...", 10); }
}

When should you use Atomic classes?

JAVA
package com.bank.core;

import java.math.BigDecimal;
import java.util.concurrent.atomic.*;

public class AtomicUseCases {
    // Counter: atomic increment
    private final AtomicInteger txnCount = new AtomicInteger(0);

    // Sequence generator: unique trade IDs
    private final AtomicLong tradeSequence = new AtomicLong(0);

    // One-time initialization flag
    private final AtomicBoolean initialized = new AtomicBoolean(false);

    // Immutable reference swap with CAS check
    private final AtomicReference<BigDecimal> maxPrice =
        new AtomicReference<>(BigDecimal.ZERO);

    // High-contention counter: per-thread cells
    private final LongAdder requestCounter = new LongAdder();

    public void recordTransaction(BigDecimal price) {
        txnCount.incrementAndGet();
        requestCounter.increment();

        // Atomic max update
        maxPrice.getAndUpdate(current ->
            price.compareTo(current) > 0 ? price : current);
    }

    public long generateTradeId() {
        return tradeSequence.incrementAndGet();
    }

    public void initOnce() {
        if (initialized.compareAndSet(false, true)) {
            // runs exactly once across all threads
            System.out.println("Initialized!");
        }
    }
}

How do volatile and Atomic compare?

| Feature | volatile | Atomic classes |
|---|---|---|
| Visibility | Yes | Yes |
| Atomicity | No | Yes (CAS-based) |
| Mutual exclusion | No | No |
| Use for flags | Yes | AtomicBoolean also works |
| Use for counters | No (counter++ races) | Yes (incrementAndGet) |
| Use for CAS operations | No | Yes (compareAndSet) |
| Multi-variable invariants | No | No (use synchronized) |
| Performance | Lightest | Light (CAS, no lock) |

What is the decision guide?

  • Need only visibility (read/write, no compound op)? Use volatile.
  • Need atomic increment or decrement? Use AtomicInteger or AtomicLong.
  • Need atomic conditional update (CAS)? Use AtomicXxx.compareAndSet().
  • Need high-throughput counter (many threads)? Use LongAdder.
  • Need atomic reference swap? Use AtomicReference.
  • Need to prevent the ABA problem? Use AtomicStampedReference.
  • Need compound operation on multiple variables? Use synchronized.

What should a senior engineer know?

  • ABA problem: CAS checks if the value is the same as expected. But another thread may have changed it from A to B and back to A. CAS sees A and succeeds, missing the intermediate change. AtomicStampedReference adds a version stamp to detect ABA.
  • VarHandle (Java 9): provides fine-grained memory ordering modes beyond volatile. getOpaque(), getAcquire(), setRelease() offer weaker-than-volatile ordering for performance-sensitive code.
  • Contention profiling: use perf stat or JMH to measure CAS retries. If retry rate is high, switch from AtomicLong to LongAdder.

Visual Overview

DIAGRAM
  Decision Guide:

  Does the new value depend on the old value?
  ├── NO:  volatile (flags, status, immutable object swap)
  └── YES: Is it a single variable?
           ├── YES: AtomicInteger / AtomicLong / AtomicReference
           │        High contention? LongAdder
           └── NO:  synchronized (multi-variable invariant)

Key Takeaway

Use volatile when you need visibility only: a single variable with simple read/write where the new value does not depend on the old value. Shutdown flags, latest configuration, and status strings. Use Atomic classes when you need visibility plus atomicity: counters, sequence generators, conditional updates, and reference swaps. AtomicInteger for low-to-medium contention, LongAdder for high contention.

Neither volatile nor Atomic classes are sufficient for multi-variable invariants or complex compound operations. For those, use synchronized or ReentrantLock.

Remember one sentence: volatile is for visibility of simple reads and writes, Atomic classes are for atomic compound operations on single variables, and synchronized is for everything else.

---

Share
Use ← → arrow keys to navigate