Atomics & Compare-And-Swap — Java Interview Guide | Cracked Java
Senior

Atomics & Compare-And-Swap

Lock-free programming primitives: CAS, the Atomic* classes, the ABA problem, LongAdder under contention, and the VarHandle API that replaced Unsafe.

Prereqs: jmm-happens-before

Atomics are the JDK's lock-free toolbox. Instead of guarding a mutable field with a lock, the java.util.concurrent.atomic classes update it with a single hardware instruction — compare-and-swap (CAS) — so threads contend on a CPU primitive rather than blocking on a monitor. The payoff is no context switches, no deadlock, and no priority inversion; the cost is a retry loop and a few sharp edges (ABA, write contention) that a senior engineer is expected to know.

The CAS primitive

CAS takes three arguments: a memory location, an expected value, and a new value. The hardware atomically checks whether the location still holds the expected value and, if so, writes the new one — all in one uninterruptible step (lock cmpxchg on x86, CASAL/LL-SC on ARM). It returns whether the swap happened. Lock-free algorithms wrap this in a read-modify-CAS-retry loop: read the current value, compute the next, and CAS; if another thread won the race, the CAS fails and you loop with the fresh value.

        read current value (v)
            |
      compute next = f(v)
            |
      CAS(addr, expected=v, next)
         /        \
     success      failure (someone else changed it)
        |              |
      done   <---------+  (loop: re-read, recompute)
The CAS retry loop

What the JDK gives you

AtomicInteger, AtomicLong, AtomicReference, and the array/field-updater variants expose this loop as methods: incrementAndGet, compareAndSet, getAndUpdate, accumulateAndGet. Each holds a volatile field, so reads and writes carry the same visibility and ordering guarantees as volatile — CAS is a full happens-before edge. Under heavy write contention the single hot field becomes a bottleneck (every failed CAS is wasted work and cache-line ping-pong), which is why LongAdder exists: it stripes the count across multiple cells.

ABA and the modern API

CAS only checks the value, not whether it changed and changed back. If a value goes A → B → A, a stale CAS succeeds even though the world moved underneath it — the ABA problem. The fix is to version the reference: AtomicStampedReference pairs a value with a counter so A-with-stamp-1 differs from A-with-stamp-3. Underneath all of this, Atomic* classes are built on VarHandle (since Java 9), the supported, type-safe replacement for the internal sun.misc.Unsafe that powered them historically.

Questions

6 in this topic