Atomics & Lock-Free Thinking
Compare-and-swap, the atomic classes, and how lock-free algorithms avoid the cost and deadlock risk of locking.
Deutsche Übersetzung in Arbeit
Diese Lektion ist noch nicht ins Deutsche übersetzt und wird daher auf Englisch angezeigt. Der Rest der Seite ist vollständig lokalisiert.
Auf dieser Seite
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 updatesCompare-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 itThis 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.
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.
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.