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.
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 one2. 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:7The 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 fail3. 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.
Full-Stack Internet Architecture
Introducing full-stack Internet architecture technologies centered on Java
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.