Start Learning
Javaneer
Back to stage
Stage 7·Advanced Concurrency & Modern Java

Atomics & Lock-Free Thinking

Compare-and-swap, the atomic classes, and how lock-free algorithms avoid the cost and deadlock risk of locking.

15 min readAdvanced
On this page

Locks are the obvious way to protect shared state, but they have costs: contention, context switches, and the ever-present risk of deadlock. For many problems there's a faster, deadlock-free alternative built on a single hardware instruction: compare-and-swap. Understanding it, and the atomic classes built on it, is core senior-level concurrency knowledge.

The problem with count++

Recall from the memory-model lesson that count++ is three operations - read, add, write - so two threads can interleave and lose updates. synchronized fixes it but serializes access. The atomic classes fix it without a lock:

AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();   // atomic read-modify-write, no lock, no lost updates

Compare-and-swap (CAS)

Atomics are built on compare-and-swap, a CPU instruction that does "if this memory location still holds the value I expect, set it to the new value - atomically." If another thread changed it in the meantime, the CAS fails and you retry:

// what incrementAndGet does under the hood, conceptually:
int current, next;
do {
    current = value.get();          // read the current value
    next = current + 1;             // compute the new value
} while (!value.compareAndSet(current, next));   // retry if someone else changed it

This is lock-free: no thread ever blocks another. Under low-to-moderate contention it's much faster than locking (no OS-level parking/waking), and it cannot deadlock because there's no lock to hold. The trade-off is that under very high contention many CAS retries can waste CPU (a "livelock"-ish spin), where a lock might be better.

The atomic toolkit

Java's java.util.concurrent.atomic package offers AtomicInteger, AtomicLong, AtomicReference (CAS on an object reference - useful for lock-free stacks and updating immutable snapshots), and LongAdder (a high-throughput counter that shards updates across cells to reduce contention). Reach for these before writing a synchronized block around a single variable.

The ABA problem

CAS checks that a value is unchanged, but a value could change from A to B and back to A between your read and your CAS - which the CAS can't detect, potentially corrupting lock-free data structures. This is the ABA problem. AtomicStampedReference addresses it by pairing the value with a version stamp that changes on every update. It rarely bites simple counters, but it's the kind of subtlety senior interviews probe.

Editing a shared doc with 'save only if unchanged'

Locking is like taking exclusive edit access to a document - nobody else can touch it until you release, and if two people both wait for each other's documents, everyone's stuck (deadlock). CAS is the optimistic approach: you read the current version, make your change, and hit 'save only if the document is still at the version I read.' If someone else saved first, your save is rejected and you simply re-read and retry. Nobody is ever locked out, so no deadlock - but if dozens of people hammer the same paragraph, you'll retry a lot, and occasionally a plain lock is calmer.

Atomic counter vs. synchronized

You need a counter incremented by many threads. Compare using an AtomicInteger against a synchronized block around an int, in terms of correctness, performance, and deadlock risk.

What does compare-and-swap (CAS) do, and why is it valuable?

Key takeaways

  • Atomic classes (AtomicInteger, AtomicReference, LongAdder) provide thread-safe updates without locks.
  • They're built on compare-and-swap: atomically set a value only if it still equals the expected one, else retry.
  • Lock-free updates never block another thread and cannot deadlock - faster than locking under low/moderate contention.
  • Under very high contention, CAS retries can waste CPU; LongAdder shards counts, and sometimes a lock is calmer.
  • The ABA problem (value changes A->B->A undetected) can corrupt lock-free structures; AtomicStampedReference adds a version stamp.
Was this lesson helpful?
Edit this page on GitHub