Atomic Variables
Use AtomicInteger, AtomicLong, and AtomicReference for lock-free thread-safe operations.
Overview
Key Points
- •AtomicInteger, AtomicLong, AtomicBoolean for primitive wrappers
- •AtomicReference<T> for object references
- •AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray for arrays
- •incrementAndGet() is equivalent to ++i (returns new value)
- •getAndIncrement() is equivalent to i++ (returns old value)
- •compareAndSet(expected, update) is the core CAS operation
- •getAndUpdate(fn) and updateAndGet(fn) apply a function atomically
- •accumulateAndGet(x, fn) accumulates using a binary operator
- •LongAdder is faster than AtomicLong for high-contention counters
- •Atomic variables do not eliminate race conditions in compound operations across multiple variables
Examples
AtomicInteger as a thread-safe counter
import java.util.concurrent.atomic.AtomicInteger;
public class Main {
static AtomicInteger counter = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100_000; i++) counter.incrementAndGet();
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 100_000; i++) counter.incrementAndGet();
});
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println(counter.get()); // always 200000
}
}With a plain int and no synchronization, the result would be less than 200000 due to lost updates. AtomicInteger guarantees all increments are reflected.
compareAndSet for optimistic locking patterns
import java.util.concurrent.atomic.AtomicInteger;
public class Main {
public static void main(String[] args) {
AtomicInteger value = new AtomicInteger(10);
// Only update if current value is still 10
boolean updated = value.compareAndSet(10, 20);
System.out.println("Updated: " + updated + ", value: " + value.get()); // true, 20
// Now value is 20, so CAS with expected=10 fails
updated = value.compareAndSet(10, 30);
System.out.println("Updated: " + updated + ", value: " + value.get()); // false, 20
// Custom accumulation: multiply by 2 atomically
int result = value.updateAndGet(v -> v * 2);
System.out.println("After doubling: " + result); // 40
}
}compareAndSet is the building block for all atomic operations. updateAndGet retries internally if a concurrent write happens between the read and the CAS, so the function may be called more than once.
AtomicReference for lock-free object swapping
import java.util.concurrent.atomic.AtomicReference;
public class Main {
record Config(String host, int port) {}
static AtomicReference<Config> config =
new AtomicReference<>(new Config("localhost", 8080));
public static void main(String[] args) {
// Hot-swap config without locking
Config old = config.get();
Config updated = new Config(old.host(), 9090);
boolean swapped = config.compareAndSet(old, updated);
System.out.println("Swapped: " + swapped);
System.out.println("New config: " + config.get());
}
}AtomicReference is perfect for immutable config objects that are periodically replaced. Because Config is a record (immutable), reading threads always see a consistent snapshot without needing locks.
Common Mistakes
- Using AtomicInteger but still doing non-atomic compound reads. For example, reading get() and then conditionally calling set() is not atomic; another thread can modify the value between the get() and set(). Use compareAndSet or updateAndGet instead.
- Assuming AtomicReference prevents all races. It only makes the reference swap atomic. If the referenced object is mutable, readers can still observe partial state. Prefer immutable value objects with AtomicReference.
- Using AtomicLong in a highly contended hot path. LongAdder performs better under contention because it shards the counter across multiple cells and sums them on read.
- Calling get() twice expecting the same value. Between two get() calls, another thread can change the value. Store the result once: int v = atomic.get(); then work with v.
Note
LongAdder and LongAccumulator (Java 8+) outperform AtomicLong when many threads increment a counter simultaneously. The tradeoff is that sum() does not return a consistent snapshot if updates are ongoing, but for aggregate counters (like hit counts) that is acceptable.
Exercises
Write Java code and click Run to execute it instantly. When your output looks correct, mark as done.
Completed: 0 / 2 exercises
Knowledge Check
Question 1 of 4