The java.util.concurrent.atomic package provides single-variable updates that are atomic without locking. AtomicInteger, AtomicLong, and AtomicReference let multiple threads update a shared value correctly using a hardware primitive — compare-and-swap — instead of a mutex. For hot counters and flags this is dramatically faster than synchronized, because there is no blocking, no context switch, and no lock to contend.
Compare-and-swap: the primitive underneath
Every atomic class is built on compare-and-swap (CAS), a single CPU instruction that atomically does: 'if this memory location still holds the value I expect, replace it with a new value, and tell me whether it worked'. The update loop retries until its expectation holds:
// conceptually what incrementAndGet does
int prev, next;
do {
prev = value.get();
next = prev + 1;
} while (!value.compareAndSet(prev, next)); // retry if another thread changed itBecause CAS is non-blocking, a thread never sleeps waiting for a lock — it either succeeds or retries immediately. This is lock-free: the system as a whole always makes progress even if individual threads retry. Under low-to-moderate contention it crushes lock-based counters.
The everyday atomic operations
The common methods, and what each guarantees atomically:
| Method | Does |
|---|---|
incrementAndGet() | atomic ++value, returns new |
getAndAdd(n) | atomic value += n, returns old |
compareAndSet(exp, new) | set to new iff current == exp; returns success |
updateAndGet(fn) | apply fn in a CAS loop for you |
accumulateAndGet(x, fn) | combine current with x via fn, atomically |
AtomicReference<State> ref = new AtomicReference<>(initial);
ref.updateAndGet(s -> s.withIncrementedVersion()); // lock-free state transitionupdateAndGet/accumulateAndGet take a pure, side-effect-free function — the CAS loop may call it multiple times on contention, so it must be safe to re-run.
The ABA problem
CAS checks that a value equals what you expect — not that it never changed. If another thread changes A→B→A between your read and your CAS, your compareAndSet(A, ...) succeeds even though the value moved and came back. For a plain counter this is harmless; for a reference-based structure (a lock-free stack popping nodes) it can corrupt state — the node you thought was still on top may have been removed, freed, and a different node reused at the same address.
AtomicStampedReference solves it by pairing the value with an int 'stamp' you bump on every change; the CAS then checks value and stamp, so an A→B→A round trip is detected because the stamp advanced. Reach for it whenever the identity of a reference matters, not just its current value.
LongAdder: when contention is high
Under heavy write contention, a single AtomicLong degrades: dozens of threads CAS the same memory word, most fail and retry, and the cache line ping-pongs between cores. LongAdder (JDK 8) fixes this by striping the counter across multiple internal cells; each thread updates its own cell, and sum() adds them up when you read.
LongAdder hits = new LongAdder();
hits.increment(); // updates a per-thread cell, minimal contention
long total = hits.sum(); // adds the cells -- not an atomic snapshotThe trade: sum() is not a precise atomic instant (cells can change while it adds), and it uses more memory. So for a high-throughput metric counter, prefer LongAdder; for a value you must read-modify-write atomically as a single unit (a sequence generator, a limit you enforce), keep AtomicLong.
Atomic field updaters and VarHandle
If you want atomic updates on an existing volatile field without paying for an Atomic* wrapper object per instance, AtomicIntegerFieldUpdater and friends CAS a named volatile field reflectively. In modern code VarHandle (JDK 9) is the successor: a typed, faster handle to a field or array element supporting compareAndSet, getAndAdd, and fine-grained memory-ordering modes (getAcquire, setRelease, getVolatile). Libraries building lock-free structures use VarHandle to avoid a wrapper object per field while keeping full control over ordering.
When atomics are the wrong tool
Atomics guarantee atomicity for one variable. The moment your invariant spans two fields — 'balance and lastTxnId must update together' — independent atomics cannot help; another thread can observe one updated and the other not. That is a job for a lock (or an immutable object swapped via a single AtomicReference). Also, a CAS retry loop that spins under pathological contention can waste CPU; if you see that, a lock that parks the thread may actually be cheaper. Use atomics for single-variable hot paths; reach for locking when the atomic unit is bigger than one word.
AtomicStampedReference), switch to LongAdder under heavy write contention, and remember atomics protect exactly one variable: if your invariant spans multiple fields, you still need a lock or an immutable object swapped through one AtomicReference.