Fundamentals 16 min read

Understanding Optimistic and Pessimistic Locks, CAS, ABA Problem, and Java Lock Implementations

This article explains the concepts of optimistic and pessimistic locking, the CAS mechanism and its ABA issue, and demonstrates Java implementations such as AtomicInteger, AtomicStampedReference, ReentrantReadWriteLock, and various lock types with practical code examples.

Top Architect
Top Architect
Top Architect
Understanding Optimistic and Pessimistic Locks, CAS, ABA Problem, and Java Lock Implementations

The article begins by defining optimistic locks, which assume low contention and only check for conflicts during updates, and introduces their implementation using Compare‑And‑Swap (CAS). It describes the CAS workflow, shows a diagram, and explains how Java's sun.misc.Unsafe class underpins CAS operations.

Next, the article discusses the use of AtomicInteger to achieve thread‑safe counters, providing a complete Java example that increments and decrements a shared counter in parallel threads.

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 使用AtomicInteger保证线程安全问题
 */
public class AtomicIntegerDemo {
    static class Counter {
        private static AtomicInteger num = new AtomicInteger(0);
        private int MAX_COUNT = 100000;
        public Counter(int MAX_COUNT) { this.MAX_COUNT = MAX_COUNT; }
        // ++ 方法
        public void increment() {
            for (int i = 0; i < MAX_COUNT; i++) {
                num.getAndIncrement();
            }
        }
        // -- 方法
        public void decrement() {
            for (int i = 0; i < MAX_COUNT; i++) {
                num.getAndDecrement();
            }
        }
        public int getNum() { return num.get(); }
    }
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter(100000);
        Thread thread1 = new Thread(() -> { counter.increment(); });
        Thread thread2 = new Thread(() -> { counter.decrement(); });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("最终结果:" + counter.getNum());
    }
}

The article then outlines the limitations of CAS, including high overhead, single‑variable atomicity, and the ABA problem, which occurs when a value changes and later returns to its original state, misleading a simple CAS check.

To illustrate the ABA problem, a Java demo using AtomicInteger is provided, showing how concurrent transfers can produce incorrect results when the intermediate state is not detected.

import java.util.concurrent.atomic.AtomicInteger;

/**
 * ABA问题演示
 */
public class ABADemo1 {
    private static AtomicInteger money = new AtomicInteger(100);
    public static void main(String[] args) throws InterruptedException {
        // 第一次转账 -50,耗时 2s
        Thread t1 = new Thread(() -> {
            int old_money = money.get();
            try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
            money.compareAndSet(old_money, old_money - 50);
        });
        t1.start();
        // 第二次转账 -50,立即执行
        Thread t2 = new Thread(() -> {
            int old_money = money.get();
            money.compareAndSet(old_money, old_money - 50);
        });
        t2.start();
        // 给账户加 50,耗时 1s
        Thread t3 = new Thread(() -> {
            try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
            int old_money = money.get();
            money.compareAndSet(old_money, old_money + 50);
        });
        t3.start();
        t1.join(); t2.join(); t3.join();
        System.out.println("最终的钱数:" + money.get());
    }
}

To solve the ABA issue, the article introduces AtomicStampedReference , which adds a version stamp to each update, ensuring that both the value and its stamp match before proceeding.

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * ABA问题解决  添加版本号
 */
public class ABADemo2 {
    private static AtomicStampedReference
money =
        new AtomicStampedReference<>(100, 0);
    public static void main(String[] args) throws InterruptedException {
        // 第一次转账 -50,耗时 2s
        Thread t1 = new Thread(() -> {
            int old_money = money.getReference();
            int oldStamp = money.getStamp();
            try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
            boolean result = money.compareAndSet(old_money, old_money - 50, oldStamp, oldStamp + 1);
            System.out.println(Thread.currentThread().getName() + "转账:" + result);
        }, "线程1");
        t1.start();
        // 第二次转账 -50,立即执行
        Thread t2 = new Thread(() -> {
            int old_money = money.getReference();
            int oldStamp = money.getStamp();
            boolean result = money.compareAndSet(old_money, old_money - 50, oldStamp, oldStamp + 1);
            System.out.println(Thread.currentThread().getName() + "转账:" + result);
        }, "线程2");
        t2.start();
        // 给账户加 50,耗时 1s
        Thread t3 = new Thread(() -> {
            try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
            int old_money = money.getReference();
            int oldStamp = money.getStamp();
            boolean result = money.compareAndSet(old_money, old_money + 50, oldStamp, oldStamp + 1);
            System.out.println(Thread.currentThread().getName() + "发工资:" + result);
        }, "线程3");
        t3.start();
        t1.join(); t2.join(); t3.join();
        System.out.println("最终的钱数:" + money.getReference());
    }
}

The article then shifts to pessimistic locks, describing how they assume the worst case and lock resources preemptively, with examples of synchronized and Lock . It explains fair vs. non‑fair locks, showing how new ReentrantLock(true) creates a fair lock while the default is non‑fair.

Read‑write locks are introduced next. The article explains that multiple threads can hold a read lock simultaneously, but write locks are exclusive, and demonstrates usage of ReentrantReadWriteLock with a thread pool executing concurrent read and write tasks.

import java.time.LocalDateTime;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 演示读写锁的使用
 */
public class ReadWriteLockDemo1 {
    public static void main(String[] args) {
        final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        final ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
        final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 0, TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(100));
        // Read task 1
        executor.submit(() -> {
            readLock.lock();
            try {
                System.out.println("执行读锁1:" + LocalDateTime.now());
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) { e.printStackTrace(); }
            finally { readLock.unlock(); }
        });
        // Read task 2
        executor.submit(() -> {
            readLock.lock();
            try {
                System.out.println("执行读锁2:" + LocalDateTime.now());
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) { e.printStackTrace(); }
            finally { readLock.unlock(); }
        });
        // Write task 1
        executor.submit(() -> {
            writeLock.lock();
            try {
                System.out.println("执行写锁1:" + LocalDateTime.now());
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) { e.printStackTrace(); }
            finally { writeLock.unlock(); }
        });
        // Write task 2
        executor.submit(() -> {
            writeLock.lock();
            try {
                System.out.println("执行写锁2:" + LocalDateTime.now());
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) { e.printStackTrace(); }
            finally { writeLock.unlock(); }
        });
    }
}

Finally, the article covers re‑entrant locks (allowing the same thread to acquire the lock multiple times) and spin locks (where a thread repeatedly attempts to acquire a lock without blocking), noting that Java's synchronized uses an adaptive spin strategy.

Overall, the piece provides a comprehensive guide to Java concurrency control mechanisms, their trade‑offs, and practical code demonstrations.

JavaConcurrencyoptimistic lockCASpessimistic lockLocks
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

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.