Atomic Variables
The java.util.concurrent.atomic package provides a set of classes that support atomic operations on single variables. These classes are the foundation of lock-free programming in Java.
Source Code #
Core Mechanism: CAS (Compare-And-Swap) #
Atomic variables do not use locks. Instead, they rely on CAS (Compare-And-Swap), a low-level CPU instruction that updates a memory location only if its current value matches an expected value. This is performed atomically at the hardware level.
public class AtomicInteger extends Number {
private static final Unsafe U = Unsafe.getUnsafe();
private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value");
private volatile int value;
public final boolean compareAndSet(int expect, int update) {
return U.compareAndSetInt(this, VALUE, expect, update);
}
public final int incrementAndGet() {
return U.getAndAddInt(this, VALUE, 1) + 1;
}
}
Implementation Details #
Field Access via Unsafe #
Atomic classes use sun.misc.Unsafe for direct memory access:
private static final Unsafe U = Unsafe.getUnsafe();
private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value");
Unsafe provides:
- Direct memory access: Bypasses JVM optimizations for atomic operations
- Field offsets: Pre-computed offsets for volatile field access
- Hardware intrinsics: Maps to CPU instructions like
CMPXCHG(x86)
Core Atomic Operations #
compareAndSet (CAS):
public final boolean compareAndSet(int expectedValue, int newValue) {
return U.compareAndSetInt(this, VALUE, expectedValue, newValue);
}
getAndSet:
public final int getAndSet(int newValue) {
return U.getAndSetInt(this, VALUE, newValue);
}
Arithmetic Operations #
All arithmetic operations use getAndAdd:
public final int getAndAdd(int delta) {
return U.getAndAddInt(this, VALUE, delta);
}
public final int incrementAndGet() {
return U.getAndAddInt(this, VALUE, 1) + 1;
}
public final int addAndGet(int delta) {
return U.getAndAddInt(this, VALUE, delta) + delta;
}
Update Methods (Java 8+) #
Functional update methods use CAS loops:
public final int getAndUpdate(IntUnaryOperator updateFunction) {
int prev = get(), next = 0;
for (boolean haveNext = false;;) {
if (!haveNext)
next = updateFunction.applyAsInt(prev);
if (weakCompareAndSetVolatile(prev, next))
return prev;
haveNext = (prev == (prev = get()));
}
}
public final int updateAndGet(IntUnaryOperator updateFunction) {
int prev = get(), next = 0;
for (boolean haveNext = false;;) {
if (!haveNext)
next = updateFunction.applyAsInt(prev);
if (weakCompareAndSetVolatile(prev, next))
return next;
haveNext = (prev == (prev == get()));
}
}
Key aspects:
- Side-effect-free functions: Functions may be retried on contention
- Optimization:
haveNextflag avoids redundant function calls - Memory semantics: Uses
weakCompareAndSetVolatilefor proper memory barriers
Accumulate Methods #
public final int getAndAccumulate(int x, IntBinaryOperator accumulatorFunction) {
int prev = get(), next = 0;
for (boolean haveNext = false;;) {
if (!haveNext)
next = accumulatorFunction.applyAsInt(prev, x);
if (weakCompareAndSetVolatile(prev, next))
return prev;
haveNext = (prev == (prev = get()));
}
}
Memory Ordering Variants #
AtomicInteger provides multiple memory ordering variants:
Volatile (default):
public final boolean compareAndSet(int expect, int update) {
return U.compareAndSetInt(this, VALUE, expect, update);
}
Plain (weaker memory semantics):
public final boolean weakCompareAndSetPlain(int expect, int update) {
return U.weakCompareAndSetIntPlain(this, VALUE, expect, update);
}
Lazy Set (store-store barrier only):
public final void lazySet(int newValue) {
U.putIntRelease(this, VALUE, newValue);
}
Constructor #
public AtomicInteger(int initialValue) {
value = initialValue;
}
public AtomicInteger() {
// Default value is 0
}
Serialization #
private static final long serialVersionUID = 6214790243416807050L;
AtomicInteger extends Number and implements Serializable for compatibility.
Canonical Usage #
When to use: Use atomic variables for simple counters, flags, or state variables that are accessed by multiple threads. They are significantly faster than using synchronized or ReentrantLock for single-variable updates.
Common Patterns:
- Non-blocking Counters: Generating unique IDs or tracking request counts.
- State Flags: Using
AtomicBooleanfor thread-safe “stopped” or “initialized” flags. - Compare-and-Set Loops: Performing complex updates by reading the current value and attempting to CAS it to a new value in a loop.
AtomicInteger counter = new AtomicInteger(0);
// Basic increment
int nextId = counter.incrementAndGet();
// Complex update loop
int oldValue, newValue;
do {
oldValue = counter.get();
newValue = transform(oldValue);
} while (!counter.compareAndSet(oldValue, newValue));
Performance Trade-offs #
- Pros: Extremely low overhead. No thread parking or context switching. High throughput under low to moderate contention.
- Cons: Under extremely high contention (many threads updating the same variable), CAS operations may fail repeatedly, leading to “busy-spinning” and high CPU usage.