Concurrency

58. Atomic Classes

AtomicInteger, AtomicLong, AtomicReference, and LongAdder provide lock-free atomic operations using hardware CAS (Compare-And-Swap) โ€” more scalable than synchronized for simple counter/state updates.

Java 21 (core language)

Explanation

Compare-And-Swap (CAS): atomically check if a variable still has an expected value; if yes, replace it with a new value; if no (another thread changed it), retry. The CPU provides this as a single instruction โ€” no lock required.

AtomicInteger / AtomicLong: incrementAndGet(), decrementAndGet(), addAndGet(delta), compareAndSet(expected, update), getAndUpdate(fn). All atomic, all lock-free.

AtomicReference<T>: atomic get/set/CAS on an object reference. Beware the ABA problem: a reference changes from A to B then back to A โ€” a CAS on A succeeds even though the value changed. Use AtomicStampedReference<T> (integer version stamp) to detect ABA.

LongAdder: designed for high-contention counting. Instead of one shared counter, it maintains a cell per CPU/thread and only sums on sum(). Far lower contention than AtomicLong when many threads increment simultaneously โ€” but sum() is approximate in the presence of concurrent updates (still correct when no concurrent updates occur at the time of sum()).

Example

import java.util.concurrent.atomic.*;

public class AtomicClasses {
    public static void main(String[] args) throws InterruptedException {
        // AtomicInteger: lock-free counter
        AtomicInteger counter = new AtomicInteger(0);
        Thread[] threads = new Thread[100];
        for (int i = 0; i < 100; i++) {
            threads[i] = new Thread(() -> { for (int j = 0; j < 100; j++) counter.incrementAndGet(); });
            threads[i].start();
        }
        for (Thread t : threads) t.join();
        System.out.println("Counter (always 10000): " + counter.get());

        // compareAndSet: CAS operation
        AtomicInteger val = new AtomicInteger(10);
        boolean swapped = val.compareAndSet(10, 20); // expected=10, update=20
        System.out.println("CAS succeeded: " + swapped + ", val=" + val.get()); // true, 20

        boolean failed = val.compareAndSet(10, 99); // expected=10 but val=20 โ€” fails
        System.out.println("CAS failed: " + !failed + ", val=" + val.get()); // true, 20

        // AtomicReference: atomic object reference swap
        AtomicReference<String> ref = new AtomicReference<>("initial");
        ref.updateAndGet(s -> s.toUpperCase());
        System.out.println("Ref: " + ref.get()); // INITIAL

        // LongAdder: high-contention counters
        LongAdder adder = new LongAdder();
        Thread[] adderThreads = new Thread[1000];
        for (int i = 0; i < 1000; i++) {
            adderThreads[i] = new Thread(() -> adder.increment());
            adderThreads[i].start();
        }
        for (Thread t : adderThreads) t.join();
        System.out.println("Adder sum (always 1000): " + adder.sum());
    }
}

Sources