Fundamentals 15 min read

Understanding CAS, AtomicInteger, and Unsafe in Java Concurrency

This article explains Java's memory model, the limitations of synchronized and volatile, introduces the java.util.concurrent.atomic package, details the CAS algorithm, the role of the Unsafe class, common pitfalls like the ABA problem, and presents code examples and solutions such as AtomicStampedReference.

Full-Stack Internet Architecture
Full-Stack Internet Architecture
Full-Stack Internet Architecture
Understanding CAS, AtomicInteger, and Unsafe in Java Concurrency

Java's memory model must guarantee visibility, atomicity, and ordering. Before JDK 5, synchronization relied on the synchronized keyword, which is a pessimistic, exclusive lock that can cause thread contention and reduced efficiency.

The lightweight synchronization mechanism volatile provides visibility but not atomicity, prompting the introduction of the java.util.concurrent.atomic package, which offers a suite of atomic classes.

1. Atomic Classes

Atomic classes ensure that a thread executing an atomic method cannot be interrupted, behaving like a spin lock where other threads wait for the operation to complete. They achieve non‑blocking atomicity by leveraging hardware‑level instructions.

Atomic classes are grouped into:

Basic types: AtomicBoolean , AtomicInteger , AtomicLong

Array types: AtomicIntegerArray , AtomicLongArray , AtomicReferenceArray

Reference types: AtomicReference , AtomicMarkableReference , AtomicStampedReference

Field updaters: AtomicIntegerFieldUpdater , AtomicLongFieldUpdater , AtomicReferenceFieldUpdater

JDK 1.8 additions: DoubleAccumulator , LongAccumulator , DoubleAdder , LongAdder , Striped64

Common AtomicInteger methods are listed in the table below:

Method                Description
get()                 Directly returns the current value
addAndGet(int)        Adds the given value and returns the result (i++)
getAndAdd(int)        Adds the given value but returns the previous value (++i)
getAndIncrement()     Increments by 1 and returns the previous value
getAndDecrement()     Decrements by 1 and returns the previous value
getAndSet(int)        Sets to the given value and returns the previous value
incrementAndGet()     Increments by 1 and returns the new value
decrementAndGet()     Decrements by 1 and returns the new value
floatValue()          Returns the value as a float
intValue()            Returns the value as an int
set(int)              Sets the value directly
lazySet(int)          Sets the value lazily (only visible on a subsequent get)
compareAndSet(int,int)Attempts to set a new value if the current value matches the expected one

2. What is CAS?

2.1 CAS Algorithm

CAS stands for Compare and Swap , a CPU‑level synchronization primitive that atomically compares a memory location's current value (V) with an expected value (A) and, if they match, swaps it with a new value (B). It is a lock‑free, non‑blocking technique.

2.2 Analyzing AtomicInteger with CAS

Most AtomicInteger methods delegate to the Unsafe class, which provides native access to low‑level memory operations.

public final class Unsafe {
    private static final Unsafe theUnsafe;
    // ...
    public native int getInt(Object o, long offset);
    public native void putInt(Object o, long offset, int x);
    public native boolean compareAndSwapInt(Object o, long offset, int expected, int newValue);
    // ...
}

The field valueOffset stores the memory offset of the value field, allowing Unsafe to read and modify it directly:

public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

The value field itself is declared volatile , guaranteeing visibility across threads.

2.3 Example: Thread‑Safe Increment

public class CASDemo {
    public static void main(String[] args) {
        System.out.println(num.compareAndSet(6, 7) + "\t + current num:" + num);
        System.out.println(num.compareAndSet(6, 7) + "\t current num:" + num);
    }
}
// Output example:
true    + current num:7
false   current num:7

The differing results illustrate CAS's optimistic nature: the second call fails because the value has already been updated.

2.4 ABA Problem

The ABA issue occurs when a value changes from A to B and back to A between a read and a CAS operation, causing the CAS to mistakenly believe the value is unchanged.

2.5 Solving ABA with Version Stamps

Classes like AtomicStampedReference attach a version stamp to the reference, allowing detection of intermediate changes.

public class AtomicStampedReferenceDemo {
    static AtomicStampedReference
asf = new AtomicStampedReference<>("A", 1);
    public static void main(String[] args) {
        new Thread(() -> {
            String value = asf.getReference();
            System.out.println("Thread1 current value: " + asf.getReference() + ", stamp: " + asf.getStamp());
            asf.compareAndSet(value, "B", asf.getStamp(), asf.getStamp() + 1);
            System.out.println("Thread1: " + value + "——>" + asf.getReference() + ", stamp:" + asf.getStamp());
            value = asf.getReference();
            asf.compareAndSet(asf.getReference(), "A", asf.getStamp(), asf.getStamp() + 1);
            System.out.println("Thread1: " + value + "——>" + asf.getReference() + ", stamp:" + asf.getStamp());
        }).start();

        new Thread(() -> {
            String value = asf.getReference();
            int stamp = asf.getStamp();
            System.out.println("Thread2 current value: " + asf.getReference() + ", stamp: " + stamp);
            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            boolean flag = asf.compareAndSet(value, "B", stamp, stamp + 1);
            System.out.println(flag ? "Thread2 update success" : "Thread2 update fail");
        }).start();
    }
}
// Sample output:
Thread1 current value: A, stamp: 1
Thread2 current value: A, stamp: 1
Thread1:A——>B,stamp:2
Thread1:B——>A,stamp:3
Thread2 update fail

3. Drawbacks of CAS

Long spin loops can cause high CPU overhead when contention is high.

CAS guarantees atomicity only for a single variable; coordinating multiple variables still requires locks.

The ABA problem, as described above.

Understanding these limitations helps developers choose the right concurrency primitive for a given scenario.

JavaconcurrencyCASUnsafeABAAtomicInteger
Full-Stack Internet Architecture
Written by

Full-Stack Internet Architecture

Introducing full-stack Internet architecture technologies centered on Java

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.