Backend Development 15 min read

Understanding AtomicInteger, CAS, and Lock‑Free Concurrency in Java

This article explains how Java's AtomicInteger and related atomic classes provide lock‑free thread‑safe operations to replace non‑atomic constructs like i++, detailing their inheritance, underlying Unsafe mechanisms, CAS implementation, memory barriers, optimistic locking, the ABA problem, and practical code examples for increment, decrement, and custom CAS‑based locks.

Full-Stack Internet Architecture
Full-Stack Internet Architecture
Full-Stack Internet Architecture
Understanding AtomicInteger, CAS, and Lock‑Free Concurrency in Java

This is the 119th original article from the "Java Builder" series, focusing on how to achieve thread‑safe increment‑like operations in Java using atomic utilities instead of the non‑atomic i++ .

AtomicInteger Overview

Since JDK 1.5, the JDK provides lock‑free atomic classes such as AtomicInteger and AtomicBoolean . These classes rely on volatile and the low‑level sun.misc.Unsafe API to guarantee atomicity without using synchronized .

AtomicInteger extends Number , just like the wrapper class Integer . Its basic fields include the internal value that holds the integer.

The class uses Unsafe.objectFieldOffset to obtain the memory offset of value , enabling direct native operations.

Constructors

AtomicInteger() creates an instance with an initial value of 0 ; AtomicInteger(int initialValue) allows specifying the starting value.

Core Methods

get() – atomically reads the current value.

set(int newValue) – atomically writes a new value.

getAndIncrement() – returns the current value and then atomically increments (equivalent to i++ ).

incrementAndGet() – atomically increments first, then returns the new value.

getAndDecrement() and decrementAndGet() – analogous decrement operations.

lazySet(int newValue) – a weaker set that may delay the memory barrier.

getAndSet(int newValue) – atomically sets a new value and returns the old one.

compareAndSet(int expect, int update) – the classic CAS operation: if the current value equals expect , it is atomically replaced with update .

weakCompareAndSet – behaves like compareAndSet but may fail spuriously.

CAS Implementation Details

The CAS methods delegate to Unsafe.compareAndSwapInt , a native method implemented in C/C++ inside the HotSpot VM. The native code ultimately uses the CPU instruction cmpxchg (compare‑and‑exchange) to achieve atomic compare‑and‑swap.

On multiprocessor systems the instruction is prefixed with LOCK to ensure exclusive access across CPUs.

Optimistic Locking and the ABA Problem

Because AtomicInteger provides only atomicity, not mutual exclusion, it can suffer from the ABA problem: a value may change from A to B and back to A, making a CAS check falsely succeed. Java solves this with AtomicStampedReference , which pairs a reference with a version stamp.

Code Examples

Increment test:

public class TAtomicTest implements Runnable {
    AtomicInteger atomicInteger = new AtomicInteger();
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println(atomicInteger.getAndIncrement());
        }
    }
    public static void main(String[] args) {
        TAtomicTest tAtomicTest = new TAtomicTest();
        Thread t1 = new Thread(tAtomicTest);
        Thread t2 = new Thread(tAtomicTest);
        t1.start();
        t2.start();
    }
}

Decrement test:

class TAtomicTestDecrement implements Runnable {
    AtomicInteger atomicInteger = new AtomicInteger(20000);
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println(atomicInteger.getAndDecrement());
        }
    }
    public static void main(String[] args) {
        TAtomicTestDecrement t = new TAtomicTestDecrement();
        new Thread(t).start();
        new Thread(t).start();
    }
}

Custom CAS‑based lock (CASLock):

class CASLock {
    AtomicInteger atomicInteger = new AtomicInteger();
    Thread currentThread = null;
    public void tryLock() throws Exception {
        boolean isLock = atomicInteger.compareAndSet(0, 1);
        if (!isLock) {
            throw new Exception("Lock acquisition failed");
        }
        currentThread = Thread.currentThread();
        System.out.println(currentThread + " tryLock");
    }
    public void unlock() {
        int lockValue = atomicInteger.get();
        if (lockValue == 0) return;
        if (currentThread == Thread.currentThread()) {
            atomicInteger.compareAndSet(1, 0);
            System.out.println(currentThread + " unlock");
        }
    }
    public static void main(String[] args) {
        CASLock casLock = new CASLock();
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    casLock.tryLock();
                    Thread.sleep(10000);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    casLock.unlock();
                }
            }).start();
        }
    }
}

Further Discussion

The article ends with two open questions: can CAS guarantee visibility between variables, and where to find the C++ source of getIntVolatile ?

Readers are invited to explore these topics further.

JavaconcurrencyCASlock-freeUnsafeAtomicIntegerOptimisticLocking
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.