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 it

Because 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.

Advertisement

The everyday atomic operations

The common methods, and what each guarantees atomically:

MethodDoes
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 transition

updateAndGet/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 snapshot

The 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.

Advertisement

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.

Atomic classes give lock-free single-variable updates via a hardware compare-and-swap retry loop — far faster than a lock for hot counters and flags. Watch for ABA on references (use 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.