Home/Learn/Java A–Z/Atomic Classes

Atomic Classes

Intermediate
Concurrency

java.util.concurrent.atomic provides lock-free thread-safe variables using hardware Compare-And-Swap (CAS) operations.

Overview

The java.util.concurrent.atomic package provides atomic variables (AtomicInteger, AtomicLong, AtomicBoolean, AtomicReference) that perform read-modify-write operations atomically without locks. Internally they use CPU-level CAS (Compare-And-Swap) instructions — much faster than synchronized for single-variable operations. LongAdder and LongAccumulator (Java 8+) are even faster for high-contention counters.

AtomicInteger and AtomicLong

AtomicInteger wraps an int with atomic operations: incrementAndGet, decrementAndGet, addAndGet, compareAndSet, getAndUpdate, updateAndGet. All operations are atomic — no race conditions even without synchronization.

getAndIncrement() returns the old value then increments. incrementAndGet() increments then returns the new value.

AtomicInteger.java
import java.util.concurrent.atomic.*;

AtomicInteger counter = new AtomicInteger(0);

// Basic operations
counter.incrementAndGet();     // 1
counter.decrementAndGet();     // 0
counter.addAndGet(5);          // 5
counter.getAndIncrement();     // 5 (returns old, then increments to 6)
int current = counter.get();   // 6

// compareAndSet — CAS: only sets if current == expected
boolean updated = counter.compareAndSet(6, 10); // true
boolean failed  = counter.compareAndSet(6, 20); // false (current is 10)

// getAndUpdate / updateAndGet (Java 8+)
counter.updateAndGet(n -> n * 2);              // 20
int old = counter.getAndUpdate(n -> n + 100); // 20, counter = 120

// Use in a counter across many threads
AtomicInteger hits = new AtomicInteger(0);
IntStream.range(0, 1000).parallel()
    .forEach(i -> hits.incrementAndGet());
System.out.println(hits.get()); // Always 1000

AtomicReference and AtomicReferenceFieldUpdater

AtomicReference<V> provides atomic compare-and-set for object references — essential for lock-free data structures and safe lazy initialisation.

The ABA problem: CAS sees the expected value A, but between the read and the CAS, another thread changed it to B then back to A. AtomicStampedReference adds a stamp (version number) to detect this.

AtomicReference.java
AtomicReference<String> ref = new AtomicReference<>("initial");

// Atomic compare-and-set
boolean swapped = ref.compareAndSet("initial", "updated"); // true
System.out.println(ref.get()); // "updated"

// Lazy singleton via AtomicReference
public class LazyConfig {
    private static final AtomicReference<Config> config =
        new AtomicReference<>(null);

    public static Config get() {
        Config c = config.get();
        if (c != null) return c;
        Config newConfig = loadConfig();
        // Only first successful CAS wins; others return the winner's value
        config.compareAndSet(null, newConfig);
        return config.get();
    }
}

// AtomicStampedReference — solves ABA problem
AtomicStampedReference<String> stamped =
    new AtomicStampedReference<>("A", 0);
int[] stampHolder = new int[1];
String val = stamped.get(stampHolder); // val="A", stamp=0
stamped.compareAndSet("A", "B", 0, 1); // succeeds
stamped.compareAndSet("B", "A", 1, 2); // back to A, stamp=2
// Old CAS(A,X,0) would fail — stamp mismatch

LongAdder for High-Contention Counters

AtomicLong works well for low-contention. Under high contention, many threads retry their CAS operations. LongAdder (Java 8) solves this by maintaining multiple cells — each thread updates its own cell, reducing contention. sum() aggregates all cells.

LongAdder is significantly faster than AtomicLong for increment-heavy workloads with many threads.

LongAdder.java
import java.util.concurrent.atomic.*;

// AtomicLong — good, but contention under heavy concurrency
AtomicLong atomicCounter = new AtomicLong(0);

// LongAdder — better for high-throughput counting
LongAdder adder = new LongAdder();
LongAdder adder2 = new LongAdder();

// Concurrent increment from many threads
IntStream.range(0, 100_000).parallel()
    .forEach(i -> {
        adder.increment();
        adder2.add(2);
    });

System.out.println(adder.sum());  // 100000
System.out.println(adder2.sum()); // 200000
// LongAdder.sum() is not atomic — use in single-threaded reduce phase

// LongAccumulator — general form with custom operation
LongAccumulator max = new LongAccumulator(Long::max, Long.MIN_VALUE);
IntStream.range(0, 1000).parallel()
    .forEach(max::accumulate);
System.out.println(max.get()); // 999

Interactive Visualization

NEWRUNNABLERUNNINGBLOCKEDWAITINGTERMINATED
synchronized(lock)— free
main
RUNNING
t1
NEW
t2
NEW
main thread creates Thread t1 and Thread t2. Both are in NEW state.
1 / 6

Key Points to Remember

  • Atomic classes use CPU CAS instructions — lock-free and faster than synchronized for single variables.
  • incrementAndGet() returns new value; getAndIncrement() returns old value.
  • compareAndSet(expected, update) only updates if current == expected — the core of CAS.
  • LongAdder is faster than AtomicLong under high contention — uses per-thread cells.
  • AtomicStampedReference solves the ABA problem by pairing a value with a version stamp.

Practice Atomic Classes in the Playground

Run and modify code directly in your browser - no setup needed.

Interview Questions

Sign in to ask Aria
1

What is Compare-And-Swap (CAS) and how do atomic classes use it?

MediumAmazon
2

What is the difference between AtomicLong and LongAdder?

MediumGoogle
3

What is the ABA problem in CAS-based algorithms?

HardOracle
4

When would you use AtomicReference instead of volatile?

MediumMicrosoft
5

How does LongAdder achieve higher throughput than AtomicLong under contention?

HardNetflix

Ask Aria about Atomic Classes

Your personal AI tutor — ask anything about this concept