Skip to main content
  1. Java Concurrency (java.util.concurrent)/

Atomic Variables

4 mins

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 #

View Source on GitHub

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: haveNext flag avoids redundant function calls
  • Memory semantics: Uses weakCompareAndSetVolatile for 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 AtomicBoolean for 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.