【Java技术】volatile与CAS:无锁编程的底层奥秘

volatile与CAS:无锁编程的底层奥秘

前言

volatile和CAS(Compare-And-Swap)是Java并发编程中最基础也最重要的概念之一。它们是Java内存模型(JMM)的基石,也是理解JUC并发包的钥匙。很多开发者知道volatile保证可见性、CAS是原子操作,但对其底层原理、实现机制以及适用场景往往一知半解。本文将从CPU指令级别深入剖析这两个概念。

一、并发编程的三大问题

1.1 可见性问题

/**
 * 可见性问题演示
 */
public class VisibilityProblem {

    private static boolean flag = true;  // 非volatile

    public static void main(String[] args) throws InterruptedException {
        // 线程1:不断检查flag
        Thread t1 = new Thread(() -> {
            int count = 0;
            while (flag) {
                count++;
                // 问题:线程1可能永远看不到线程2对flag的修改
            }
            System.out.println("线程1退出,count=" + count);
        });

        // 线程2:修改flag
        Thread t2 = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {}
            flag = false;  // 修改flag
            System.out.println("线程2修改flag为false");
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();
    }
}

/**
 * 问题分析:
 * 
 * 1. 线程1在CPU Core1上运行,将flag加载到Core1的缓存中
 * 2. 线程2在CPU Core2上运行,修改了flag(写到Core2的缓存或主内存)
 * 3. 由于CPU缓存一致性协议的实现问题,Core1可能看不到这个修改
 * 4. 线程1陷入死循环(不可见)
 */

1.2 有序性问题

/**
 * 有序性问题(指令重排序)
 */
public class OrderingProblem {

    private int a = 0;
    private int b = 0;
    private int x = 0;
    private int y = 0;

    public void reorderScenario() throws InterruptedException {
        int iterations = 0;

        while (true) {
            iterations++;
            a = 0;
            b = 0;
            x = 0;
            y = 0;

            Thread t1 = new Thread(() -> {
                a = 1;      // 操作1
                x = b;      // 操作2
            });

            Thread t2 = new Thread(() -> {
                b = 1;      // 操作3
                y = a;      // 操作4
            });

            t1.start();
            t2.start();
            t1.join();
            t2.join();

            // 正常情况下,可能的结果:(1,1), (1,0), (0,1)
            // 但不可能是(0,0)吗?让我们测试
            if (x == 0 && y == 0) {
                System.out.println("发现重排序导致的结果: x=0, y=0");
                System.out.println("迭代次数: " + iterations);
                break;
            }
        }
    }
}

/**
 * 问题分析:
 * 
 * 在单线程环境下,编译器/CPU可能对指令重排序以优化性能
 * 线程1的指令顺序可能被重排为:
 *   x = b;    // 先读取b
 *   a = 1;    // 再写入a
 * 
 * 这种重排序在单线程下是安全的
 * 但在多线程环境下可能导致违反直觉的结果
 */

1.3 原子性问题

/**
 * 原子性问题演示
 */
public class AtomicityProblem {

    private static int counter = 0;  // 非volatile,非原子

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[1000];

        for (int i = 0; i < 1000; i++) {
            threads[i] = new Thread(() -> {
                counter++;  // 非原子操作
            });
            threads[i].start();
        }

        for (Thread t : threads) {
            t.join();
        }

        // 期望结果:1000
        // 实际结果:可能是956、987等,小于1000
        System.out.println("最终counter值: " + counter);
    }
}

/**
 * 问题分析:
 * 
 * counter++ 看似一个操作,实际上包含三个步骤:
 * 1. LOAD   :从内存读取counter到CPU寄存器
 * 2. ADD     :CPU寄存器中+1
 * 3. STORE   :将结果写回内存
 * 
 * 多线程执行时:
 * 线程1: LOAD counter(0) → ADD → STORE counter=1  ✓
 * 线程2: LOAD counter(0) → ADD → STORE counter=1  ✓
 *        以上假设执行顺利,但...
 * 
 * 线程1: LOAD counter(0)
 * 线程2: LOAD counter(0)  ← 两个线程同时读取
 * 线程1: ADD → STORE counter=1
 * 线程2: ADD → STORE counter=1  ← 覆盖了线程1的结果
 *        结果:counter=1,丢失了一次更新!
 */

二、volatile详解

2.1 volatile的作用

/**
 * volatile保证的两大特性:
 * 1. 可见性:写操作对其他线程立即可见
 * 2. 有序性:禁止指令重排序
 */
public class VolatileDemo {

    // 使用volatile修饰,解决可见性问题
    private volatile static boolean flag = true;

    // volatile保证有序性的场景
    private volatile int x = 0;
    private volatile int y = 0;

    public void orderedWrite() {
        // volatile写之前的指令不能重排到volatile写之后
        a = 1;          // 普通写
        x = 1;          // volatile写 - 屏障
        // x写之前的指令不能重排到x写之后

        // volatile读之后的指令不能重排到volatile读之前
        int r1 = x;     // volatile读 - 屏障
        int r2 = a;     // 普通读
        // r2读不能重排到r1读之前
    }
}

2.2 volatile底层实现:内存屏障

/**
 * JMM定义的4种内存屏障:
 * 
 * StoreLoad Barriers:
 *   - 最重的屏障,兼顾Store和Load
 *   - 防止之前的写操作与之后的读操作重排序
 *   - 在x86架构下会生成MFENCE指令
 * 
 * 各种volatile操作的屏障:
 * 
 * volatile读:
 *   在读操作后插入LoadLoad屏障和LoadStore屏障
 *   ┌─────────────────────────────┐
 *   │ Load                        │
 *   │ LoadLoad Barrier            │
 *   │ LoadStore Barrier           │
 *   └─────────────────────────────┘
 * 
 * volatile写:
 *   在写操作前插入StoreStore屏障
 *   在写操作后插入StoreLoad屏障
 *   ┌─────────────────────────────┐
 *   │ StoreStore Barrier          │
 *   │ Store (volatile write)      │
 *   │ StoreLoad Barrier           │
 *   └─────────────────────────────┘
 */

2.3 volatile在JMM中的语义

┌─────────────────────────────────────────────────────────────────────┐
│                    volatile 读写与内存屏障                           │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  volatile写操作:                                                   │
│                                                                     │
│  Thread A (写入)                   Thread B (读取)                   │
│      │                               │                              │
│      │                               │                              │
│      ▼                               │                              │
│  ┌─────────┐                          │                              │
│  │ 普通写1 │                          │                              │
│  └────┬────┘                          │                              │
│       │                               │                              │
│       │  StoreStore Barrier            │                              │
│       ├───────────────────────────────┤                              │
│       │                               │                              │
│       ▼                               │                              │
│  ┌─────────────────┐                  │                              │
│  │ volatile write  │ ─────────────────┼───► 可见(刷新到主内存)    │
│  └────────┬────────┘                  │                              │
│           │                            │                              │
│           │  StoreLoad Barrier          │                              │
│           ├────────────────────────────┤                              │
│           │                            ▼                              │
│           │                   ┌─────────────────┐                   │
│           │                   │ LoadStore Barrier│                   │
│           │                   └────────┬────────┘                   │
│           │                            │                              │
│           │                            ▼                              │
│           │                   ┌─────────────────┐                    │
│           │                   │ volatile read   │ ◄── 强制从主内存读取 │
│           │                   └────────┬────────┘                    │
│           │                            │                              │
│           │                            ▼                              │
│           │                   ┌─────────────────┐                    │
│           │                   │ 普通读          │                    │
│           │                   └─────────────────┘                    │
│           │                            │                              │
│           │                   ┌─────────────────┐                    │
│           │                   │ LoadLoad Barrier │                   │
│           │                   └─────────────────┘                    │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

2.4 volatile的使用场景

/**
 * volatile的适用场景:
 * 
 * 1. 状态标志
 * 2. 一次性安全发布(双重检查锁定的单例)
 * 3. 观察者模式中的Observable对象
 * 4. 替代锁的简单计数
 */

public class VolatileUsage {

    // 场景1:状态标志
    private volatile boolean running = true;

    public void stop() {
        running = false;  // 线程立即看到停止信号
    }

    // 场景2:一次性发布
    private volatile Object instance;

    public Object getInstance() {
        if (instance == null) {  // 第一次检查
            synchronized (this) {
                if (instance == null) {  // 第二次检查
                    instance = new Object();  // 创建对象
                }
            }
        }
        return instance;
    }

    // 场景3:计数器(需要复合操作的场景不能用volatile)
    private volatile long counter = 0;

    // ❌ 错误:counter++不是原子操作
    public void incrementWrong() {
        counter++;  // 不安全!
    }

    // ✅ 正确:使用AtomicLong
    private AtomicLong safeCounter = new AtomicLong(0);

    public void incrementCorrect() {
        safeCounter.incrementAndGet();  // 原子操作
    }
}

三、CAS详解

3.1 CAS原理

/**
 * CAS (Compare-And-Swap) 原理:
 * 
 * CAS(V, A, B) 操作包含三个参数:
 * - V:内存地址
 * - A:期望值
 * - B:新值
 * 
 * 操作语义:
 * 如果V的值等于A,则将B写入V
 * 否则,不做任何操作
 * 返回V的当前值
 * 
 * 这是一个原子操作,由CPU硬件保证
 */

// Java中CAS的使用
public class CASDemo {

    private AtomicInteger value = new AtomicInteger(0);

    public void increment() {
        // getAndIncrement内部使用CAS
        value.getAndIncrement();
    }

    public boolean compareAndSwap(int expected, int newValue) {
        // CAS操作:只有当前值等于expected时,才设置为newValue
        return value.compareAndSet(expected, newValue);
    }
}

/**
 * CAS在JVM中的实现:
 * 
 * JVM会调用Unsafe类的CAS方法
 * Unsafe类封装了CPU的CAS指令
 * 
 * 例如 x86架构下的 CMPXCHG 指令
 */

3.2 CPU指令级别的CAS

┌─────────────────────────────────────────────────────────────────────┐
│                         CAS 硬件实现                                 │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  x86架构使用 CMPXCHG (Compare and Exchange) 指令:                  │
│                                                                     │
│  CMPXCHG [mem], reg                                                │
│                                                                     │
│  语义:                                                            │
│  IF (EAX == [mem]) THEN  // 比较EAX寄存器和内存值                    │
│      ZF = 1;                  // 设置零标志                          │
│      [mem] = reg;            // 交换                                │
│  ELSE                       // 不相等                              │
│      ZF = 0;                  // 清零标志                            │
│      EAX = [mem];            // 更新EAX为内存值                      │
│  FI                                                               │
│                                                                     │
│  多核情况下的CAS使用LOCK前缀:                                      │
│  LOCK CMPXCHG [mem], reg                                          │
│                                                                     │
│  LOCK前缀的作用:                                                   │
│  1. 在修改前锁定CPU缓存行(Cache Lock)                            │
│  2. 强制写操作对其他CPU可见(Store Buffer失效)                     │
│  3. 确保原子性                                                     │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

3.3 CAS的ABA问题

/**
 * ABA问题:
 * 
 * 线程1读取内存值A
 * 线程2将A改为B,再改回A
 * 线程1进行CAS操作,发现值还是A,认为没有被修改过
 * 但实际上已经被修改过了!
 */

public class ABAProblem {

    public void demonstrateABA() {
        AtomicInteger value = new AtomicInteger(1);

        System.out.println("初始值: " + value.get());

        // 线程2:修改为2,再改回1
        Thread t2 = new Thread(() -> {
            value.set(2);
            value.set(1);
            System.out.println("线程2修改完成: " + value.get());
        });

        // 线程1:期望1变为3,但实际是1
        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {}
            boolean success = value.compareAndSet(1, 3);
            System.out.println("线程1 CAS结果: " + success + 
                             ", 值: " + value.get());
        });

        t2.start();
        t1.start();
    }
}

/**
 * ABA问题的解决方案:
 * 1. AtomicStampedReference:带版本号的引用
 * 2. AtomicMarkableReference:带标记位的引用
 */

// 使用AtomicStampedReference解决ABA问题
public class ABASolution {

    private AtomicStampedReference<Integer> value = 
        new AtomicStampedReference<>(1, 0);

    public void correctCAS() {
        int stamp = value.getStamp();
        Integer current = value.getReference();

        // 只有值和版本号都匹配时才更新
        boolean success = value.compareAndSet(
            current,       // 期望值
            current + 1,   // 新值
            stamp,         // 期望版本号
            stamp + 1      // 新版本号
        );
    }
}

3.4 CAS的缺点

/**
 * CAS的缺点:
 * 
 * 1. ABA问题(可使用AtomicStampedReference解决)
 * 2. 高竞争下大量失败重试,浪费CPU
 * 3. 只能保证单个变量的原子性
 * 4. 复杂数据结构需要手工实现
 */

public class CASLimitations {

    // 缺点1:竞争激烈时的性能问题
    public void contentionDemo() {
        AtomicInteger counter = new AtomicInteger(0);

        // 1000个线程同时竞争
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                // 激烈竞争下,大量CAS失败
                while (!counter.compareAndSet(
                    counter.get(), 
                    counter.get() + 1)) {
                    // 自旋重试,浪费CPU
                    Thread.yield();  // 让出CPU
                }
            }).start();
        }
    }

    // 缺点2:无法保证复合操作的原子性
    public void compoundOperation() {
        AtomicInteger balance = new AtomicInteger(100);

        // ❌ 这不是原子操作!
        // 可能导致负数余额
        if (balance.get() >= 100) {  // 检查
            try {
                Thread.sleep(1);  // 检查和扣款之间可能被其他线程修改
            } catch (InterruptedException e) {}
            balance.set(balance.get() - 100);  // 扣款
        }

        // ✅ 使用AtomicInteger的正确方法
        // 循环直到成功
        while (true) {
            int current = balance.get();
            if (current >= 100) {
                if (balance.compareAndSet(current, current - 100)) {
                    break;  // 成功
                }
                // 失败,重试
            } else {
                break;  // 余额不足
            }
        }
    }
}

四、无锁编程实战

4.1 使用Atomic原子类

import java.util.concurrent.atomic.*;

/**
 * Java提供的原子类:
 * 
 * 基本类型:
 * AtomicInteger, AtomicLong, AtomicBoolean
 * 
 * 引用类型:
 * AtomicReference, AtomicMarkableReference, AtomicStampedReference
 * 
 * 数组:
 * AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray
 * 
 * 属性更新器:
 * AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, 
 * AtomicReferenceFieldUpdater
 */

public class AtomicClassesDemo {

    // 基本类型原子类
    private AtomicInteger atomicInt = new AtomicInteger(0);

    // 引用类型原子类
    private AtomicReference<User> userRef = new AtomicReference<>();

    // 数组原子类
    private AtomicIntegerArray intArray = new AtomicIntegerArray(10);

    public void atomicDemo() {
        // AtomicInteger常用方法
        atomicInt.getAndIncrement();      // i++
        atomicInt.incrementAndGet();      // ++i
        atomicInt.getAndDecrement();     // i--
        atomicInt.decrementAndGet();     // --i
        atomicInt.getAndAdd(5);          // i += 5
        atomicInt.addAndGet(5);          // +=5后返回
        atomicInt.compareAndSet(10, 20); // CAS: if(i==10) i=20

        // lambda更新(JDK 8+)
        atomicInt.updateAndGet(x -> x + 1);  // x -> x + 1
        atomicInt.accumulateAndGet(5, Integer::sum);  // x + 5

        // 引用类型
        userRef.compareAndSet(null, new User("Tom"));
    }
}

4.2 实现无锁线程安全单例

/**
 * 双重检查锁定单例 vs 无锁单例
 */

public class SingletonPatterns {

    // 双重检查锁定(需要volatile)
    public static class DoubleCheckedSingleton {
        private static volatile DoubleCheckedSingleton instance;

        public static DoubleCheckedSingleton getInstance() {
            if (instance == null) {  // 第一次检查
                synchronized (DoubleCheckedSingleton.class) {
                    if (instance == null) {  // 第二次检查
                        instance = new DoubleCheckedSingleton();
                        // new操作分解为:
                        // 1. 分配内存
                        // 2. 调用构造函数
                        // 3. 赋值给instance
                        // volatile防止指令重排序,确保2在3之前完成
                    }
                }
            }
            return instance;
        }
    }

    // 无锁单例(基于CAS)
    public static class CASBasedSingleton {
        private static final AtomicReference<CASBasedSingleton> instance = 
            new AtomicReference<>();

        public static CASBasedSingleton getInstance() {
            CASBasedSingleton current = instance.get();
            if (current == null) {
                current = new CASBasedSingleton();
                if (!instance.compareAndSet(null, current)) {
                    // 竞争失败,有其他线程已经创建
                    current = instance.get();
                }
            }
            return current;
        }
    }

    // 静态内部类(利用类加载机制,推荐)
    public static class StaticInnerSingleton {
        private static class SingletonHolder {
            static final StaticInnerSingleton instance = 
                new StaticInnerSingleton();
        }

        public static StaticInnerSingleton getInstance() {
            return SingletonHolder.instance;
        }
    }

    // 枚举单例(最安全,推荐)
    public enum EnumSingleton {
        INSTANCE;

        public void doSomething() {
            System.out.println("EnumSingleton doing something");
        }
    }
}

4.3 无锁队列实现

import java.util.concurrent.atomic.*;

/**
 * 基于CAS的无锁队列(简化版)
 */
public class LockFreeQueue<E> {

    private static class Node<E> {
        volatile E item;
        volatile Node<E> next;

        Node(E item) {
            this.item = item;
        }
    }

    private final AtomicReference<Node<E>> head;
    private final AtomicReference<Node<E>> tail;

    public LockFreeQueue() {
        Node<E> dummy = new Node<>(null);
        head = new AtomicReference<>(dummy);
        tail = new AtomicReference<>(dummy);
    }

    /**
     * 入队操作
     * 使用CAS确保只有一个线程能成功修改tail
     */
    public void enqueue(E item) {
        Node<E> newNode = new Node<>(item);
        newNode.next.set(null);  // 新节点next初始化为null

        while (true) {
            Node<E> currentTail = tail.get();
            Node<E> tailNext = currentTail.next.get();

            // 再次检查tail是否还是尾部
            if (currentTail == tail.get()) {
                if (tailNext != null) {
                    // 队列正在更新tail,帮助推进tail
                    tail.compareAndSet(currentTail, tailNext);
                } else {
                    // 尝试将新节点连接到尾部
                    if (currentTail.next.compareAndSet(null, newNode)) {
                        // 成功,尝试推进tail
                        tail.compareAndSet(currentTail, newNode);
                        return;
                    }
                }
            }
        }
    }

    /**
     * 出队操作
     * 使用CAS确保只有一个线程能成功修改head
     */
    public E dequeue() {
        while (true) {
            Node<E> currentHead = head.get();
            Node<E> currentTail = tail.get();
            Node<E> headNext = currentHead.next.get();

            if (currentHead == head.get()) {
                if (currentHead == currentTail) {
                    // 队列为空
                    if (headNext == null) {
                        return null;
                    }
                    // tail落后,帮助推进
                    tail.compareAndSet(currentTail, headNext);
                } else {
                    // 队列非空
                    E item = headNext.item;
                    // 尝试更新head
                    if (head.compareAndSet(currentHead, headNext)) {
                        return item;
                    }
                    // 失败,重试
                }
            }
        }
    }
}

五、性能对比

5.1 synchronized vs volatile vs CAS

/**
 * 性能对比:
 * 
 * synchronized:
 * - 可重入锁
 * - 阻塞线程,线程切换开销大
 * - 适用场景:写多、竞争激烈、需要保证复合操作原子性
 * 
 * volatile:
 * - 非阻塞
 * - 无线程切换开销
 * - 适用场景:状态标志、一次性发布、简单读写
 * 
 * CAS (Atomic*):
 * - 非阻塞
 * - 失败重试,CPU开销
 * - 适用场景:计数器、简单复合操作、高读低写
 */

public class PerformanceComparison {

    private int counter1 = 0;  // 普通变量
    private volatile int counter2 = 0;  // volatile
    private AtomicInteger counter3 = new AtomicInteger(0);  // CAS

    // synchronized计数器
    private int counter4 = 0;
    public synchronized void incrementSync() {
        counter4++;
    }

    public void benchmark(int iterations) throws InterruptedException {
        // 1. 普通变量(无同步,多个线程各跑各的)
        Thread[] threads1 = new Thread[4];
        for (int i = 0; i < 4; i++) {
            threads1[i] = new Thread(() -> {
                for (int j = 0; j < iterations; j++) {
                    counter1++;  // 竞态条件,结果不可靠
                }
            });
        }

        // 2. volatile(可见性保证,但++非原子)
        Thread[] threads2 = new Thread[4];
        for (int i = 0; i < 4; i++) {
            threads2[i] = new Thread(() -> {
                for (int j = 0; j < iterations; j++) {
                    counter2++;  // 结果不可靠
                }
            });
        }

        // 3. AtomicInteger(CAS保证原子性)
        Thread[] threads3 = new Thread[4];
        for (int i = 0; i < 4; i++) {
            threads3[i] = new Thread(() -> {
                for (int j = 0; j < iterations; j++) {
                    counter3.incrementAndGet();  // 原子操作
                }
            });
        }

        // 4. synchronized
        Thread[] threads4 = new Thread[4];
        for (int i = 0; i < 4; i++) {
            threads4[i] = new Thread(() -> {
                for (int j = 0; j < iterations; j++) {
                    incrementSync();  // 同步操作
                }
            });
        }

        // 执行并计时...
    }
}

/**
 * 总结:
 * 
 * | 方案         | 可见性 | 原子性 | 性能 | 适用场景              |
 * |--------------|--------|--------|------|------------------------|
 * | 普通变量     | ×      | ×      | 最快 | 单线程                |
 * | volatile     | ✓      | ×      | 快   | 状态标志              |
 * | Atomic*      | ✓      | ✓      | 快   | 计数器、简单复合操作   |
 * | synchronized | ✓      | ✓      | 中   | 复杂复合操作          |
 */

总结

volatile和CAS是Java并发编程的两大基石:

  1. volatile的作用
  2. 保证可见性:写操作立即刷新到主内存,读操作立即从主内存读取
  3. 保证有序性:通过内存屏障防止指令重排序
  4. 不保证原子性:复合操作(如i++)仍需要其他同步手段

  5. CAS的原理

  6. 硬件级别的原子指令(x86的CMPXCHG)
  7. 三个操作在一个原子指令中完成
  8. 解决ABA问题需要使用带版本号的引用类型

  9. 选择策略

  10. 简单状态标志 → volatile
  11. 计数器、简单复合操作 → Atomic*类
  12. 复杂复合操作 → synchronized或Lock

  13. 注意事项

  14. volatile不能替代锁
  15. CAS在高竞争下有性能问题
  16. ABA问题在特定场景下可能导致bug
  17. 无锁编程需要仔细验证正确性

理解这两者的底层原理,是深入学习Java并发包、设计高性能并发系统的基础。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值