Atomicity is one of the three cornerstones of thread safety alongside visibility and ordering. A set of operations is atomic when it appears indivisible to other threads—either all steps complete or none do, with exclusive access at any moment. Visibility ensures that modifications made by one thread become immediately apparent to others; ordering addresses the sequence of instructions as observed across threads, which can be distorted by CPU instruction reordering.
The Compare-and-Swap (CAS) instruction, provided by modern processors, enables lightweight atomic operations without resorting to heavy locking. A CAS operation accepts three operands: a memory location, an expected old value, and a new value. It atomically checks whether the current value at the memory location equals the expected value; if so, it replaces it with the new value. Otherwise, it does nothing. In Java, classes such as AtomicInteger leverage CAS inside a loop to implement non‑blocking atomic updates.
Consider the classic non‑atomic counter:
static int counter = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
counter++;
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
counter++;
}
});
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println(counter);
}
Because counter++ is not atomic, the final value is often less than 20000. Replacing the plain int with an AtomicInteger solves this:
static AtomicInteger atomicCounter = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
atomicCounter.incrementAndGet();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
atomicCounter.incrementAndGet();
}
});
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println(atomicCounter.get());
}
Now the output is reliably 20000. The incrementAndGet() method internally performs a CAS loop until the update succeeds, guaranteeing atomicity without expliict locks.
Despite its efficiency, CAS has several drawbacks. The ABA problem arises when a value changes from A to B and back to A; a CAS check would wrongly assume nothing changed. Solutions involve attaching a version number, as implemented in AtomicStampedReference. Another issue is that a CAS loop may spin indefinitely under high contention, wasting CPU cycles. Furthermore, CAS intrinsically atomically updates only a single memory location. To operate on multiple variables atomically, you can encapsulate them into an immutable object and use AtomicReference.
Java exposes low‑level CAS through the sun.misc.Unsafe class. The following snippet illustrates a custom atomic counter built directly on Unsafe:
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class UnsafeCounter {
private volatile int value;
private static final Unsafe U;
private static final long VALUE_OFFSET;
static {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
U = (Unsafe) f.get(null);
VALUE_OFFSET = U.objectFieldOffset(UnsafeCounter.class.getDeclaredField("value"));
} catch (Exception e) {
throw new Error(e);
}
}
public void increment() {
int current;
do {
current = U.getIntVolatile(this, VALUE_OFFSET);
} while (!U.compareAndSwapInt(this, VALUE_OFFSET, current, current + 1));
}
public int get() {
return U.getIntVolatile(this, VALUE_OFFSET);
}
public static void main(String[] args) throws InterruptedException {
UnsafeCounter uc = new UnsafeCounter();
Thread t1 = new Thread(() -> { for (int i=0; i<10000; i++) uc.increment(); });
Thread t2 = new Thread(() -> { for (int i=0; i<10000; i++) uc.increment(); });
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println(uc.get()); // always 20000
}
}
Java ships with a rich set of atomic utilities under the java.util.concurrent.atomic package.
AtomicInteger provides methods such as addAndGet(int delta), compareAndSet(int expect, int update), getAndIncrement(), and getAndSet(int newValue). AtomicIntegerArray atomically updates elements in an integer array; it copies the supplied array upon construction, so modifications do not affect the original. AtomicLongArray behaves similarly for long arrays.
For reference types, AtomicReference<V> allows atomic updates to a single object reference. This becomes useful when you need to change an immutable holder containing several fields. AtomicStampedReference<V> pairs a reference with an integer stamp, effectively solving the ABA problem by versioning every update. AtomicMarkableReference<V> couples a reference with a boolean marker, suitable for scenarios that only require a "touched/untouched" indication.
Field updaters enable atomic operations on volatile fields of existing classes. AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, and AtomicReferenceFieldUpdater can be created through their static newUpdater() method. The fields they target must be declared public volatile, and the updater ensures that threads see consistent values via volatile semantics while performing CAS on the field's memory offset.
These atomic facilities form the backbone of many lock‑free data structures and high‑performance concurrent algorithms in the Java platform.