Atomic Methods (Modern Java)
Critique of the legacy text. The original page mixed Java 1.4–era “atomic method lists” with brief one-line descriptions, didn’t explain why atomics exist, and equated “blocked on I/O or sync” with all forms of blocking. It also omitted today’s most useful types (LongAdder, LongAccumulator) and modern guidance on when to choose atomics vs. locks. This rewrite centers on intent, correctness, and trade-offs, with small, runnable examples.
Use the java.util.concurrent atomic classes and lock framework to implement correct, scalable concurrency
What “atomic” means
Atomic variables perform updates as a single, indivisible action using CAS (compare-and-set) under the hood. CAS avoids heavyweight locking, making single-variable updates fast and contention-friendly. Atomic reads/writes have happens-before semantics like volatile.
Core classes you’ll actually use
AtomicBoolean, AtomicInteger, AtomicLong, AtomicReference<T> - single-value CAS with utilities such as getAndIncrement(), addAndGet(), getAndSet(), compareAndSet().
LongAdder / LongAccumulator - high-throughput counters/reducers that beat AtomicLong under heavy contention (e.g., metrics).
VarHandle (advanced) - fine-grained atomic/memory-ordering operations when you need low-level control.
Why non-atomic "++" is a trap
Even a single line like count++ is a read-modify-write sequence and is not atomic. Multiple threads can interleave and lose increments.
public class Counter {
private int count; // not atomic
public void increment() { count++; } // data race
public int getValue() { return count; }
}
Fix with AtomicInteger
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicCounter {
private final AtomicInteger count = new AtomicInteger();
public void increment() {
count.incrementAndGet(); // atomic RMW
}
public int getValue() {
return count.get(); // atomic read
}
}
High contention? Prefer LongAdder
LongAdder spreads updates across cells and aggregates on read, trading a slightly slower read for much faster write under contention.
import java.util.concurrent.atomic.LongAdder;
public class Metrics {
private final LongAdder requests = new LongAdder();
public void recordRequest() { requests.increment(); }
public long snapshot() { return requests.sum(); }
}
CAS patterns with compareAndSet
Use compareAndSet(expected, update) to implement lock-free state transitions.
import java.util.concurrent.atomic.AtomicReference;
enum State { NEW, RUNNING, TERMINATED }
public class LifecycleFlag {
private final AtomicReference<State> state = new AtomicReference<>(State.NEW);
public boolean start() {
return state.compareAndSet(State.NEW, State.RUNNING);
}
public boolean stop() {
return state.getAndSet(State.TERMINATED) != State.TERMINATED;
}
}
Lazy set versus set
lazySet(v) (a.k.a. release set) eventually publishes a value with weaker ordering; set(v) publishes immediately with standard volatile semantics. Use lazySet when a slight delay is harmless (e.g., flags). For most code, set is clearer.
When to choose locks instead
Atomics shine for single-variable state and simple transitions. Once you must update multiple fields consistently or guard complex invariants, use locks:
ReentrantLock — explicit lock with lock()/unlock(), try-lock, and fairness options.
ReadWriteLock — many readers or one writer; good for mostly-read workloads.
- Prefer high-level utilities (
Semaphore, StampedLock, ConcurrentHashMap) before hand-rolled locking.
import java.util.concurrent.locks.*;
public class PriceBook {
private final ReadWriteLock rw = new ReentrantReadWriteLock();
private double price;
public void setPrice(double p) {
rw.writeLock().lock();
try { price = p; } finally { rw.writeLock().unlock(); }
}
public double getPrice() {
rw.readLock().lock();
try { return price; } finally { rw.readLock().unlock(); }
}
}
Exam-oriented quick reference
| API | What it does |
get(), set(v) | Atomic read/write with volatile semantics. |
getAndIncrement(), incrementAndGet() | Atomic ++ with old/new return. |
getAndAdd(Δ), addAndGet(Δ) | Atomic add; use LongAdder if many threads contend. |
compareAndSet(expect, update) | CAS transition; loop/retry on failure. |
getAndSet(v) | Swap atomically, returning the previous value. |
lazySet(v) | Eventually visible release write; weaker ordering. |
Takeaways
- Use atomics for single-variable state and CAS transitions.
- Use
LongAdder for hot counters/metrics; it scales better than AtomicLong.
- Reach for locks (or higher-level utilities) when multiple fields must change together.
- Avoid deprecated thread control (
stop/suspend); prefer interruptions and structured concurrency.