CAS and Java Atomic Classes Explained: Mechanics, Pitfalls, and LongAdder
This article demystifies Java's Compare‑And‑Swap (CAS) instruction, shows how atomic classes like AtomicInteger achieve lock‑free increments, discusses CAS's three main drawbacks, surveys the entire java.util.concurrent.atomic family, and explains how LongAdder improves high‑contention counting.
CAS: Optimistic hardware instruction
CAS (Compare‑And‑Swap) is an atomic CPU instruction that takes three operands: V (the memory location), A (the expected old value), and B (the new value). It atomically performs “if V == A then V = B” and returns the original value (or a boolean indicating success).
On x86 it is implemented by cmpxchg with the lock prefix, guaranteeing atomicity without any Java‑level lock.
Compared with synchronized, synchronized acquires a lock pre‑emptively (pessimistic locking), while CAS assumes low contention, writes directly and retries only when the compare fails (optimistic locking). Under heavy contention a lock may be cheaper; under light contention CAS is faster.
AtomicInteger: lock‑free increment
java.util.concurrent.atomic.AtomicIntegerimplements incrementAndGet() with a spin‑CAS loop:
public final int incrementAndGet() {
int prev, next;
do {
prev = get(); // ① read current value
next = prev + 1; // ② compute new value
} while (!compareAndSet(prev, next)); // ③ CAS, retry on failure
return next;
}The CAS call ultimately invokes sun.misc.Unsafe.compareAndSwapInt, a native method that executes the hardware instruction. Since JDK 9 the same functionality is exposed via VarHandle, a safe API wrapper around the same CAS primitive.
Three drawbacks of CAS
ABA problem : a value that changes A→B→A is seen as unchanged by CAS. For simple counters this is harmless, but for reference‑based structures it can cause logical errors. Java provides AtomicStampedReference and AtomicMarkableReference to attach a version or a boolean mark. Example:
AtomicStampedReference<Integer> ref =
new AtomicStampedReference<>(100, 0);
ref.compareAndSet(100, 101, 0, 1); // compare both value and stampSpin‑retry overhead : when many threads contend, repeated CAS failures waste CPU cycles. In such cases a lock may be more efficient. The LongAdder class is designed to mitigate this specific scenario.
Single‑variable limitation : CAS can atomically modify only one memory location. To update multiple fields atomically, either use a lock or pack the fields into an immutable object and CAS the reference via AtomicReference.
Atomic class family overview
Basic types : AtomicInteger, AtomicLong, AtomicBoolean – atomic operations on single primitive values.
Reference types : AtomicReference – CAS on an object reference, useful for grouping multiple fields.
Versioned references : AtomicStampedReference, AtomicMarkableReference – solve the ABA problem by attaching a version or a mark.
Array types : AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray – atomic operations on individual array elements.
Field updaters : AtomicIntegerFieldUpdater (and similar) – upgrade existing volatile fields to atomic updates without changing the field type.
Adders (JDK 8+) : LongAdder, LongAccumulator, DoubleAdder – high‑concurrency counting, faster than AtomicLong.
Typical usage patterns: AtomicBoolean as a one‑time execution guard:
if (inited.compareAndSet(false, true)) {
init();
} AtomicReferenceto overcome the single‑variable limitation by wrapping multiple fields into an immutable object and CAS‑ing the reference.
Field updaters are memory‑efficient when replacing a volatile field with an Atomic* instance is undesirable.
LongAdder: dispersing hotspots
When many threads contend on a single AtomicLong counter, most threads spin and fail CAS, creating a performance hotspot. LongAdder (introduced in JDK 8) uses a “divide‑and‑conquer” strategy:
Under low contention it updates a single base value via CAS, similar to AtomicLong.
If CAS on base fails, threads hash to a Cell[] array and CAS a dedicated cell, spreading the load across many slots.
The total sum is computed as base + sum of all cells.
This mirrors the segmented lock in early ConcurrentHashMap and database sharding: reducing single‑point contention by splitting it into multiple points. LongAdder.sum() does not provide a strongly consistent value in a concurrent environment because it reads each cell and the base separately while other threads may still be updating them, yielding an eventually consistent snapshot.
Use LongAdder when high‑throughput counting is required and occasional inaccuracies are acceptable (e.g., metrics, rate limiting).
Use AtomicLong (or locking) when each read must be exact (e.g., inventory deduction).
Summary
CAS is a hardware‑provided atomic “compare‑and‑swap” instruction that forms the foundation of Java’s lock‑free utilities. It enables optimistic locking: a read‑modify‑write becomes “verify‑then‑write, retry on failure”. Classes such as AtomicInteger implement lock‑free thread safety using a spin‑CAS loop. CAS has three known drawbacks—ABA, spin‑retry cost under heavy contention, and the inability to atomically modify multiple variables. LongAdder addresses the contention drawback by partitioning the counter into multiple cells, trading exactness of sum() for higher update throughput.
These primitives underlie higher‑level constructs like AQS and ConcurrentHashMap, which rely on the same lock‑free foundations.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
