Atomic Classes
Intermediatejava.util.concurrent.atomic provides lock-free thread-safe variables using hardware Compare-And-Swap (CAS) operations.
Overview
The java.util.concurrent.atomic package provides atomic variables (AtomicInteger, AtomicLong, AtomicBoolean, AtomicReference) that perform read-modify-write operations atomically without locks. Internally they use CPU-level CAS (Compare-And-Swap) instructions — much faster than synchronized for single-variable operations. LongAdder and LongAccumulator (Java 8+) are even faster for high-contention counters.
AtomicInteger and AtomicLong
AtomicInteger wraps an int with atomic operations: incrementAndGet, decrementAndGet, addAndGet, compareAndSet, getAndUpdate, updateAndGet. All operations are atomic — no race conditions even without synchronization.
getAndIncrement() returns the old value then increments. incrementAndGet() increments then returns the new value.
import java.util.concurrent.atomic.*;
AtomicInteger counter = new AtomicInteger(0);
// Basic operations
counter.incrementAndGet(); // 1
counter.decrementAndGet(); // 0
counter.addAndGet(5); // 5
counter.getAndIncrement(); // 5 (returns old, then increments to 6)
int current = counter.get(); // 6
// compareAndSet — CAS: only sets if current == expected
boolean updated = counter.compareAndSet(6, 10); // true
boolean failed = counter.compareAndSet(6, 20); // false (current is 10)
// getAndUpdate / updateAndGet (Java 8+)
counter.updateAndGet(n -> n * 2); // 20
int old = counter.getAndUpdate(n -> n + 100); // 20, counter = 120
// Use in a counter across many threads
AtomicInteger hits = new AtomicInteger(0);
IntStream.range(0, 1000).parallel()
.forEach(i -> hits.incrementAndGet());
System.out.println(hits.get()); // Always 1000AtomicReference and AtomicReferenceFieldUpdater
AtomicReference<V> provides atomic compare-and-set for object references — essential for lock-free data structures and safe lazy initialisation.
The ABA problem: CAS sees the expected value A, but between the read and the CAS, another thread changed it to B then back to A. AtomicStampedReference adds a stamp (version number) to detect this.
AtomicReference<String> ref = new AtomicReference<>("initial");
// Atomic compare-and-set
boolean swapped = ref.compareAndSet("initial", "updated"); // true
System.out.println(ref.get()); // "updated"
// Lazy singleton via AtomicReference
public class LazyConfig {
private static final AtomicReference<Config> config =
new AtomicReference<>(null);
public static Config get() {
Config c = config.get();
if (c != null) return c;
Config newConfig = loadConfig();
// Only first successful CAS wins; others return the winner's value
config.compareAndSet(null, newConfig);
return config.get();
}
}
// AtomicStampedReference — solves ABA problem
AtomicStampedReference<String> stamped =
new AtomicStampedReference<>("A", 0);
int[] stampHolder = new int[1];
String val = stamped.get(stampHolder); // val="A", stamp=0
stamped.compareAndSet("A", "B", 0, 1); // succeeds
stamped.compareAndSet("B", "A", 1, 2); // back to A, stamp=2
// Old CAS(A,X,0) would fail — stamp mismatchLongAdder for High-Contention Counters
AtomicLong works well for low-contention. Under high contention, many threads retry their CAS operations. LongAdder (Java 8) solves this by maintaining multiple cells — each thread updates its own cell, reducing contention. sum() aggregates all cells.
LongAdder is significantly faster than AtomicLong for increment-heavy workloads with many threads.
import java.util.concurrent.atomic.*;
// AtomicLong — good, but contention under heavy concurrency
AtomicLong atomicCounter = new AtomicLong(0);
// LongAdder — better for high-throughput counting
LongAdder adder = new LongAdder();
LongAdder adder2 = new LongAdder();
// Concurrent increment from many threads
IntStream.range(0, 100_000).parallel()
.forEach(i -> {
adder.increment();
adder2.add(2);
});
System.out.println(adder.sum()); // 100000
System.out.println(adder2.sum()); // 200000
// LongAdder.sum() is not atomic — use in single-threaded reduce phase
// LongAccumulator — general form with custom operation
LongAccumulator max = new LongAccumulator(Long::max, Long.MIN_VALUE);
IntStream.range(0, 1000).parallel()
.forEach(max::accumulate);
System.out.println(max.get()); // 999Interactive Visualization
Key Points to Remember
- Atomic classes use CPU CAS instructions — lock-free and faster than synchronized for single variables.
- incrementAndGet() returns new value; getAndIncrement() returns old value.
- compareAndSet(expected, update) only updates if current == expected — the core of CAS.
- LongAdder is faster than AtomicLong under high contention — uses per-thread cells.
- AtomicStampedReference solves the ABA problem by pairing a value with a version stamp.
Practice Atomic Classes in the Playground
Run and modify code directly in your browser - no setup needed.
Interview Questions
Sign in to ask AriaWhat is Compare-And-Swap (CAS) and how do atomic classes use it?
What is the difference between AtomicLong and LongAdder?
What is the ABA problem in CAS-based algorithms?
When would you use AtomicReference instead of volatile?
How does LongAdder achieve higher throughput than AtomicLong under contention?
Ask Aria about Atomic Classes
Your personal AI tutor — ask anything about this concept