Fundamentals 10 min read

Unlocking Java’s CAS: How Unsafe Powers Atomic Operations and Solves ABA

This article explains the Compare‑and‑Swap (CAS) mechanism, its hardware‑level implementation via Java’s Unsafe class, provides sample code for basic CAS, atomic adders, custom atomic types, analyzes the ABA problem, and presents solutions such as versioned stamps and AtomicStampedReference.

Xiaokun's Architecture Exploration Notes
Xiaokun's Architecture Exploration Notes
Xiaokun's Architecture Exploration Notes
Unlocking Java’s CAS: How Unsafe Powers Atomic Operations and Solves ABA

CAS Mechanism

CAS definition

CAS stands for Compare-and-swap, a hardware‑level primitive for atomic synchronization in multithreaded programs.

From a programming perspective, CAS consists of two actions: “check then act”, which are performed atomically by the processor.

CAS usage conditions

Two values are required: the expected old value and the new value to set.

The operation first checks that the current value matches the expected one.

Simple CAS version

Setting a new value using CAS.

<code>boolean cas(Object ref, int refOldVal, int refNewVal) {
    if (ref.value != refOldVal) return false;
    ref.value = refNewVal;
    return true;
}</code>
CAS adder implementation
<code>int cas_adder(Object ref, int incr) {
    while (!cas(ref, ref.value, ref.value + incr)) {
        // nothing
    }
    return ref.value;
}</code>

Java CAS via Unsafe

Atomic* classes implementation

Example: core part of AtomicBoolean.

<code>public class AtomicBoolean implements java.io.Serializable {
    private static final long serialVersionUID = 4654671469794556979L;
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;
    private volatile int value;
    static {
        try {
            valueOffset = unsafe.objectFieldOffset(AtomicBoolean.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    public final boolean compareAndSet(boolean expect, boolean update) {
        int e = expect ? 1 : 0;
        int u = update ? 1 : 0;
        return unsafe.compareAndSwapInt(this, valueOffset, e, u);
    }
}</code>

Custom CAS using Unsafe.

<code>public class AtomicDefineInt {
    private volatile int value;
    private static final Unsafe unsafe;
    private static final long iValueOffset;
    static {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            unsafe = (Unsafe) theUnsafe.get(null);
            iValueOffset = unsafe.objectFieldOffset(AtomicDefineInt.class.getDeclaredField("value"));
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
    }
    public boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, iValueOffset, expect, update);
    }
}</code>
Unsafe source and implementation
<code>public final class Unsafe {
    private static final Unsafe theUnsafe;
    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }
    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
}</code>

Analysis: Java’s CAS uses JNI to call native C++ code; for Java objects it operates directly on heap memory, otherwise it works with raw memory addresses.

Using Unsafe requires a field offset, the target object, the expected old value, and the new value; volatile ensures visibility across threads.

Problems with CAS

Issues

Spin‑wait loops for increment/decrement can degrade CPU performance.

CAS only guarantees atomicity for a single variable; multiple‑variable updates are not atomic.

The ABA problem.

ABA problem

Example implementation using AtomicDefineObject.

<code>public final class AtomicDefineObject implements Serializable {
    private volatile String value;
    private static Unsafe unsafe;
    private static final long valueOffset;
    static {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            unsafe = (Unsafe) theUnsafe.get(null);
            valueOffset = unsafe.objectFieldOffset(AtomicDefineObject.class.getDeclaredField("value"));
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
    private boolean compareAndSet(String expect, String update) {
        return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
    }
    public void setValue(String value) { this.compareAndSet(this.value, value); }
    public String getValue() { return value; }
}</code>
<code>public static void main() {
    final AtomicDefineObject obj = new AtomicDefineObject();
    obj.setValue("A");
    // three threads modify and read the value with delays
    // final read prints "A" despite intermediate changes
}</code>
ABA solution

Introduce a version number (e.g., a long counter) to detect changes.

Java provides AtomicStampedReference, which records a timestamp or stamp together with the value.

These techniques allow tracking modification history and aid in recovery after failures.

JavaconcurrencyCASAtomicUnsafe
Xiaokun's Architecture Exploration Notes
Written by

Xiaokun's Architecture Exploration Notes

10 years of backend architecture design | AI engineering infrastructure, storage architecture design, and performance optimization | Former senior developer at NetEase, Douyu, Inke, etc.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.