What are Atomic classes?
A regular count++ operation is not thread-safe:
Junior Level
Basic Understanding
Atomic classes are a set of classes from java.util.concurrent.atomic that provide thread-safe operations without locks (lock-free).
Lock-free = no mutex, no thread blocking. Uses a CAS cycle: read the value, compute the new one, try to write via CAS. If another thread changed the value — CAS returns false, repeat the cycle.
Why are they needed?
A regular count++ operation is not thread-safe:
// NOT safe!
public class UnsafeCounter {
private int count = 0;
public void increment() {
count++; // 3 operations: read → modify → write
// Two threads may read the same value!
}
}
Solution with Atomic
// Safe!
public class SafeCounter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // Atomic operation
}
}
Main Atomic Classes
| Class | Type | Example Usage |
|---|---|---|
AtomicBoolean |
boolean | State flag |
AtomicInteger |
int | Counter |
AtomicLong |
long | Counter (64-bit) |
AtomicReference<T> |
Object | Thread-safe reference |
AtomicIntegerArray |
int[] | Array of counters |
LongAdder |
long | High-load counter |
Usage Examples
// AtomicInteger
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet(); // ++i (returns new value)
counter.getAndIncrement(); // i++ (returns old value)
counter.addAndGet(5); // i += 5
counter.compareAndSet(5, 10);// If i==5, set to 10
// AtomicBoolean
AtomicBoolean flag = new AtomicBoolean(false);
flag.set(true);
boolean oldValue = flag.getAndSet(false);
// AtomicReference
AtomicReference<String> ref = new AtomicReference<>("initial");
ref.compareAndSet("initial", "updated");
Mechanism: CAS + volatile
Atomic classes work on two pillars:
volatilefield — guarantees visibility of changes- CAS (Compare-And-Swap) — atomic update
// Simplified AtomicInteger implementation
public class AtomicInteger {
private volatile int value; // volatile for visibility
public final int incrementAndGet() {
for (;;) { // Infinite loop
int current = get(); // 1. Read
int next = current + 1; // 2. Compute
if (compareAndSet(current, next)) { // 3. CAS — update
return next; // Success!
}
// CAS failed — someone else updated, try again
}
}
}
Middle Level
CAS (Compare-And-Swap) in Detail
CAS is a CPU instruction that atomically:
- Compares the value at an address with the expected value
- If equal — writes the new value
- If not equal — does nothing
// CAS pseudocode
boolean CAS(memoryAddress, expectedValue, newValue) {
if (*memoryAddress == expectedValue) {
*memoryAddress = newValue;
return true;
}
return false;
}
On x86 this is the LOCK CMPXCHG instruction.
CMPXCHG (CoMPare and eXCHanGe) — x86 instruction for CAS operation. On ARM — ldxr/stxr, on RISC-V — lr.w/sc.w (Load Reserved / Store Conditional).
Reference Updates
public class AtomicReferenceDemo {
private AtomicReference<List<String>> list = new AtomicReference<>(new ArrayList<>());
public void addItem(String item) {
list.updateAndGet(currentList -> {
// Create a new copy (immutable update)
List<String> newList = new ArrayList<>(currentList);
newList.add(item);
return newList;
});
}
}
VarHandle (Java 9+)
Starting with Java 9, Atomic classes are rewritten using VarHandle:
VarHandle (Java 9, JEP 193) — replacement for sun.misc.Unsafe for atomic operations. Safer (no direct memory access), but with the same capabilities. The API of Atomic classes remained backward compatible — no need to change your code.
// Instead of sun.misc.Unsafe — typed and safe API
public class AtomicInteger {
private static final VarHandle VALUE;
private volatile int value;
static {
try {
MethodHandles.Lookup l = MethodHandles.lookup();
VALUE = l.findVarHandle(AtomicInteger.class, "value", int.class);
} catch (ReflectiveOperationException e) {
throw new Error(e);
}
}
}
Different Memory Guarantee Levels
AtomicLong counter = new AtomicLong(0);
// Different write methods:
counter.set(100); // Volatile write — full visibility
counter.lazySet(100); // Relaxed visibility: doesn't set StoreLoad barrier, write may become visible with a delay. Faster!
counter.compareAndSet(0, 100); // CAS with full barrier
| Method | Guarantee | Speed |
|---|---|---|
set(v) |
Full visibility immediately | Standard |
lazySet(v) |
Eventual visibility | Faster |
compareAndSet |
Full barrier | Standard |
weakCompareAndSet |
May return false spuriously (even if the value matches) on some architectures. This is not a bug — allowed by the specification for optimization on CPUs with weak memory models (ARM, RISC-V). | Faster |
LongAdder for High Contention
With hundreds of threads updating a single counter, AtomicLong starts to slow down (CAS constantly fails).
// BAD under high contention:
AtomicLong counter = new AtomicLong(0);
// Every thread constantly "misses" on CAS
// GOOD under high contention:
LongAdder counter = new LongAdder();
counter.increment(); // Distributes across cells
long total = counter.sum(); // Sums all cells
How LongAdder works:
- Instead of one variable — an array of Cells
- Different threads update different cells
- On
sum(), values are summed up
When Atomic classes are NOT suitable
- Compound operations on multiple fields: need to atomically update two fields — AtomicInteger won’t help, need synchronized or AtomicReference with an immutable object
- High contention (100+ threads): CAS cycle wastes CPU — better to use LongAdder
- Long computations between read and write: if a lot of time passes between read and write — CAS will constantly fail, better to use Lock
Senior Level
Under the Hood: CAS Implementation
At the x86 CPU level:
; LOCK CMPXCHG — atomic operation
; rax = expected, rcx = newValue, [memory] = actual value
lock cmpxchg [memory], rcx
; If [memory] == rax:
; [memory] = rcx
; ZF = 1 (success)
; Else:
; rax = [memory]
; ZF = 0 (failure)
The lock prefix locks the memory bus for the duration of the operation, ensuring atomicity on multiprocessor systems.
ABA Problem
A classic CAS problem:
Thread 1: read value A
Thread 2: changed A → B → A (value returned!)
Thread 1: CAS(A, new_value) — SUCCESS! But the state changed!
Solution: AtomicStampedReference
// Stores value + version (stamp)
AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);
int[] stampHolder = new int[1];
String value = ref.get(stampHolder);
int stamp = stampHolder[0];
// CAS with version check
boolean success = ref.compareAndSet("A", "B", stamp, stamp + 1);
AtomicMarkableReference
// Stores value + boolean flag
AtomicMarkableReference<Node> ref = new AtomicMarkableReference<>(node, false);
// Mark node as "removed"
ref.compareAndSet(node, node, false, true);
LongAdder Internal Structure
// Simplified
public class LongAdder {
// Base value (used under low contention)
volatile long base;
// Cell array (used under high contention)
volatile Cell[] cells;
static final class Cell {
@Contended // Padding to avoid False Sharing
volatile long value;
}
public void add(long x) {
Cell[] as; long b, v; int m; Cell a;
if ((as = cells) != null || !casBase(b = base, b + x)) {
// Base CAS failed — use Cell
// Each thread hashes to its own cell
// ...
}
}
public long sum() {
long sum = base;
for (Cell a : cells) {
if (a != null) sum += a.value;
}
return sum;
}
}
Performance and Highload
Benchmark (approximate, 100 threads)
| Operation | Time per operation | Note |
|---|---|---|
synchronized |
~5000 ns | Context switching |
AtomicLong |
~100 ns | CAS contention |
LongAdder |
~10 ns | Distributed cells |
False Sharing
// Problem: array of Atomic elements in one cache line
AtomicInteger[] counters = new AtomicInteger[16];
// counters[0] and counters[1] may be in the same cache line
// Writing to counters[0] invalidates counters[1]
Solution — @Contended inside LongAdder:
@jdk.internal.vm.annotation.Contended
volatile long value; // 128 bytes of padding around
AtomicIntegerFieldUpdater — Memory Savings
If you have millions of objects and each needs an atomic counter:
// BAD: millions of additional AtomicInteger objects in memory
class User {
AtomicInteger loginAttempts = new AtomicInteger(0); // +24 bytes per object
}
// GOOD: updater works with a regular int field
class User {
volatile int loginAttempts = 0; // 4 bytes
private static final AtomicIntegerFieldUpdater<User> UPDATER =
AtomicIntegerFieldUpdater.newUpdater(User.class, "loginAttempts");
public void increment() {
UPDATER.incrementAndGet(this);
}
}
Diagnostics
CAS Failures in Profiler
# Java Flight Recorder — look for contention events
java -XX:StartFlightRecording=filename=rec.jfr MyApp
If lots of time in incrementAndGet — high contention.
Bytecode Analysis
javap -c -p AtomicInteger.class
getAndAddInt instructions — native, translate to lock cmpxchg.
Best Practices
- Atomic* for simple counters and flags under moderate contention
- LongAdder for high-load counters (metrics, statistics)
- AtomicReference with
updateAndGetfor immutable updates - AtomicStampedReference when the ABA problem matters
- synchronized for complex operations (multiple fields at once)
- @Contended for arrays of atomic counters (requires JVM flag)
- Avoid AtomicLong as ID generator at 1000+ RPS — use range allocation
Interview Cheat Sheet
Must know:
- Atomic classes provide lock-free thread safety via CAS + volatile
- incrementAndGet = spin cycle: read → modify → CAS retry until success
- On x86, CAS =
lock cmpxchginstruction withlockprefix (memory bus lock) - Java 9+ rewritten to use VarHandle (safe replacement for sun.misc.Unsafe, JEP 193)
- ABA problem: value changed A→B→A, CAS “won’t notice”; solution — AtomicStampedReference
- LongAdder solves contention at 100+ threads: distributes across Cell[] array, sums on sum()
lazySet()— relaxed visibility (eventual), faster thanset()— doesn’t set StoreLoad barrier
Frequent follow-up questions:
- Why does AtomicLong slow down under high contention? — CAS constantly fails, threads spin in a cycle, wasting CPU
- How does LongAdder differ from AtomicLong? — LongAdder: array of cells, each thread writes to its own; AtomicLong: one variable, all compete
- What is the ABA problem? — Thread 1 read A, Thread 2 changed A→B→A, Thread 1: CAS(A) — success, but state changed
- When to use AtomicReference? — For atomic replacement of immutable objects (e.g., configuration update)
Red flags (do NOT say):
- “Atomic classes use locks” — no, they’re lock-free via CAS
- “CAS is atomic on any architecture” — on 32-bit JVMs, AtomicLong may use an internal lock
- “lazySet() guarantees immediate visibility” — no, eventual consistency, without StoreLoad barrier
- “LongAdder is always better than AtomicLong” — no, LongAdder is faster only under high contention; for normal cases, AtomicLong
Related topics:
- [[9. What is CAS (Compare-And-Swap)]] — foundation of all Atomic classes
- [[1. What is the difference between synchronized and volatile]] — volatile for visibility + CAS for atomicity
- [[3. What is visibility problem]] — volatile field inside Atomic ensures visibility
- [[7. What is reentrant lock]] — AQS (ReentrantLock’s foundation) also uses CAS