Definition
- Lock-free –
AtomicIntegernever blocks a thread. No monitor lock, no deadlock risk, no context switching. All operations use hardware CAS. - CAS (Compare-And-Swap) – a single CPU instruction (
CMPXCHGon x86,LDREX/STREXon ARM) that atomically: reads the current value, compares it to an expected value, and if they match, writes the new value. - Retry loop –
incrementAndGet()reads the current value, computes the new value, and attempts a CAS write. If another thread changed the value between the read and the CAS, the CAS fails and the operation retries with the fresh value. - Atomic family –
AtomicInteger,AtomicLong,AtomicBoolean,AtomicReference,AtomicIntegerArray,LongAdder,AtomicStampedReference.
A mechanical token dispenser at the bank counter. You press the button and the number advances by 1. Even if 10 people press simultaneously, the mechanism physically ensures each press advances the counter. No lock on the machine. No waiting in line. The hardware resolves collisions internally.
Understanding the Concepts
What is CAS (Compare-And-Swap)?
CAS is a hardware instruction that performs an atomic conditional write. It reads the current value at a memory address, compares it to an expected value, and only writes the new value if they match.
CAS(address, expected, new):
atomically {
if (memory[address] == expected) {
memory[address] = new;
return true; // success
} else {
return false; // another thread changed it
}
}
How does AtomicInteger.incrementAndGet() work?
// Simplified from OpenJDK source
public int incrementAndGet() {
int prev, next;
do {
prev = get(); // step 1: volatile read
next = prev + 1; // step 2: compute new value
} while (!compareAndSet(prev, next)); // step 3: CAS write
return next;
// If another thread changed the value between step 1 and 3,
// compareAndSet returns false, and the loop retries.
}
What are the key AtomicInteger methods?
package com.bank.core;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerDemo {
public static void main(String[] args) {
AtomicInteger counter = new AtomicInteger(0);
counter.get(); // volatile read: 0
counter.set(100); // volatile write
counter.incrementAndGet(); // ++counter: returns 101
counter.getAndIncrement(); // counter++: returns 101, now 102
counter.decrementAndGet(); // --counter: returns 101
counter.addAndGet(50); // counter += 50: returns 151
counter.compareAndSet(151, 200); // CAS: if 151, set to 200
// Functional updates (Java 8)
counter.getAndUpdate(v -> v * 2); // returns old, sets 400
counter.updateAndGet(v -> v + 10); // sets 410, returns new
counter.accumulateAndGet(5, Integer::sum); // accumulate: 415
System.out.println("Final: " + counter.get()); // 415
}
}
Final: 415
How does AtomicInteger compare to synchronized?
package com.bank.core;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicVsSyncDemo {
private final AtomicInteger atomicCount = new AtomicInteger(0);
private int syncCount = 0;
private final Object lock = new Object();
// Lock-free: no blocking, no deadlock, no context switch
public void atomicIncrement() {
atomicCount.incrementAndGet();
}
// Blocking: monitor acquisition, possible context switch
public void syncIncrement() {
synchronized (lock) {
syncCount++;
}
}
}
| Feature | AtomicInteger | synchronized |
|---|---|---|
| Blocking | No (CAS retry) | Yes (monitor lock) |
| Deadlock risk | None | Possible (nested locks) |
| Overhead | CAS instruction | Monitor enter/exit |
| Multi-variable | No | Yes |
| Best for | Single-variable operations | Multi-variable compound ops |
When should you use AtomicInteger?
- Counters: transaction count, request count, error count.
- Sequence generators:
tradeId = atomicSeq.incrementAndGet(). - Statistics: tracking maximum via
updateAndGet(v -> Math.max(v, newValue)). - State machines:
state.compareAndSet(PENDING, ACTIVE)for state transitions.
When should you NOT use AtomicInteger?
- **Multi-variable invariants**: `low <= high` with two separate AtomicIntegers is not safe. Use synchronized. - **Check-then-act on different data**: `if (account.balance >= amount) otherAccount.credit(amount)`. Use synchronized. - **Very high contention (100+ threads)**: CAS retries degrade performance. Use `LongAdder`.
What should a senior engineer know?
- CAS on x86:
LOCK CMPXCHGinstruction. The LOCK prefix ensures the cache line is exclusively owned during the operation. - False sharing: if two AtomicIntegers are on the same cache line (64 bytes), concurrent CAS operations cause cache line bouncing. Use
@Contendedannotation or manual padding. - ABA problem: CAS checks if the value is the same, but it may have changed from A to B and back to A. Use
AtomicStampedReferenceto detect this. - VarHandle (Java 9):
AtomicIntegeris implemented usingVarHandleinternally since Java 9, replacingUnsafe.
Visual Overview
CAS Retry Loop (incrementAndGet):
Thread-1 Thread-2 value
───────── ───────── ─────
read: 0 0
read: 0 0
CAS(0 -> 1): SUCCESS 1
CAS(0 -> 1): FAIL 1
read: 1 1
CAS(1 -> 2): SUCCESS 2
Both increments succeed! No lost updates.
Lock-free: neither thread was ever blocked.
Key Takeaway
AtomicInteger provides lock-free thread safety using hardware CAS (Compare-And-Swap). The incrementAndGet() method reads the current value, computes the new value, and CAS-writes. If another thread changed the value, CAS fails and the loop retries with the fresh value. This is a single CPU instruction (CMPXCHG on x86), with no monitor lock, no blocking, no deadlock risk.
Use AtomicInteger for single-variable operations: counters, sequence generators, state transitions. Use LongAdder for high-contention counters where CAS retry overhead becomes significant. Use synchronized when you need multi-variable atomicity.
Remember one sentence: AtomicInteger uses hardware CAS to atomically read-modify-write without locks, retrying if another thread changed the value.
---