CAS:两个线程能否同时拿到值5,然后同时成功将其改为6?

126 阅读4分钟

CAS原子性简明总结

🤔 核心问题

两个线程能否同时拿到值5,然后同时成功将其改为6?

❌ 答案:绝对不可能!

🔬 实验证明

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 演示 CAS 的原子性 - 证明两个线程不可能同时成功执行 compareAndSet
 */
public class CASAtomicityDemo {

    private static AtomicInteger sharedValue = new AtomicInteger(5);
    private static AtomicLong successCount = new AtomicLong(0);
    private static AtomicLong failureCount = new AtomicLong(0);

    public static void main(String[] args) throws InterruptedException {
        System.out.println("=== CAS 原子性演示 ===");
        System.out.println("问题:两个线程能否同时将值从5改为6?");
        System.out.println("初始值: " + sharedValue.get() + "\n");

        // 测试1:简单的两线程CAS
        testTwoThreadsCAS();

        // 测试2:大量线程同时CAS
        testManyThreadsCAS();

        // 测试3:模拟你担心的场景
        testWorrisomeScenario();
    }

    /**
     * 测试两个线程同时执行CAS
     */
    private static void testTwoThreadsCAS() throws InterruptedException {
        System.out.println("🔬 测试1:两个线程同时CAS");

        sharedValue.set(5);
        CountDownLatch startLatch = new CountDownLatch(1);
        CountDownLatch doneLatch = new CountDownLatch(2);

        Thread thread1 = new Thread(() -> {
            try {
                startLatch.await(); // 等待同时开始
                boolean success = sharedValue.compareAndSet(5, 6);
                System.out.println("   线程1 CAS(5→6): " + success +
                                 ", 当前值: " + sharedValue.get());
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                doneLatch.countDown();
            }
        }, "线程1");

        Thread thread2 = new Thread(() -> {
            try {
                startLatch.await(); // 等待同时开始
                boolean success = sharedValue.compareAndSet(5, 6);
                System.out.println("   线程2 CAS(5→6): " + success +
                                 ", 当前值: " + sharedValue.get());
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                doneLatch.countDown();
            }
        }, "线程2");

        thread1.start();
        thread2.start();

        // 同时释放两个线程
        startLatch.countDown();

        // 等待完成
        doneLatch.await();

        System.out.println("   结果:只有一个线程能成功!\n");
    }

    /**
     * 测试大量线程同时CAS
     */
    private static void testManyThreadsCAS() throws InterruptedException {
        System.out.println("🔬 测试2:1000个线程同时CAS");

        sharedValue.set(100);
        successCount.set(0);
        failureCount.set(0);

        final int THREAD_COUNT = 1000;
        CountDownLatch startLatch = new CountDownLatch(1);
        CountDownLatch doneLatch = new CountDownLatch(THREAD_COUNT);

        // 创建1000个线程,都尝试将值从100改为101
        for (int i = 0; i < THREAD_COUNT; i++) {
            new Thread(() -> {
                try {
                    startLatch.await(); // 等待同时开始
                    boolean success = sharedValue.compareAndSet(100, 101);
                    if (success) {
                        successCount.incrementAndGet();
                    } else {
                        failureCount.incrementAndGet();
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    doneLatch.countDown();
                }
            }).start();
        }

        // 同时释放所有线程
        startLatch.countDown();

        // 等待完成
        doneLatch.await();

        System.out.println("   成功的线程数: " + successCount.get());
        System.out.println("   失败的线程数: " + failureCount.get());
        System.out.println("   最终值: " + sharedValue.get());
        System.out.println("   结论:" + THREAD_COUNT + "个线程中只有1个成功!\n");
    }

    /**
     * 模拟你担心的场景:详细分析CAS过程
     */
    private static void testWorrisomeScenario() throws InterruptedException {
        System.out.println("🔬 测试3:详细分析CAS执行过程");

        sharedValue.set(5);

        System.out.println("   假设的担心场景:");
        System.out.println("   1. 线程A读取值5");
        System.out.println("   2. 线程B也读取值5");
        System.out.println("   3. 线程A执行CAS(5→6)");
        System.out.println("   4. 线程B也执行CAS(5→6)");
        System.out.println("   5. 两个都成功?");
        System.out.println();

        System.out.println("   实际情况:");

        // 创建两个线程,让它们尽可能同时执行
        CountDownLatch readyLatch = new CountDownLatch(2);
        CountDownLatch startLatch = new CountDownLatch(1);
        CountDownLatch doneLatch = new CountDownLatch(2);

        Thread threadA = new Thread(() -> {
            readyLatch.countDown();
            try {
                startLatch.await();

                // 模拟读取当前值
                int currentValue = sharedValue.get();
                System.out.println("   线程A读取到值: " + currentValue);

                // 尝试CAS
                boolean success = sharedValue.compareAndSet(5, 6);
                System.out.println("   线程A CAS(5→6)结果: " + success +
                                 ", 执行后的值: " + sharedValue.get());

            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                doneLatch.countDown();
            }
        }, "线程A");

        Thread threadB = new Thread(() -> {
            readyLatch.countDown();
            try {
                startLatch.await();

                // 模拟读取当前值
                int currentValue = sharedValue.get();
                System.out.println("   线程B读取到值: " + currentValue);

                // 尝试CAS
                boolean success = sharedValue.compareAndSet(5, 6);
                System.out.println("   线程B CAS(5→6)结果: " + success +
                                 ", 执行后的值: " + sharedValue.get());

            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                doneLatch.countDown();
            }
        }, "线程B");

        threadA.start();
        threadB.start();

        // 等待两个线程都准备好
        readyLatch.await();

        // 同时开始
        startLatch.countDown();

        // 等待完成
        doneLatch.await();

        System.out.println();
        System.out.println("💡 关键理解:");
        System.out.println("   - 虽然两个线程都读取到了5");
        System.out.println("   - 但CAS操作是原子的,只有一个能成功");
        System.out.println("   - 失败的线程会在incrementAndGet()中重试");
    }
}
=== CAS 原子性演示 ===
问题:两个线程能否同时将值从5改为6?
初始值: 5

🔬 测试1:两个线程同时CAS
   线程1 CAS(5→6): false, 当前值: 6
   线程2 CAS(5→6): true, 当前值: 6
   结果:只有一个线程能成功!

🔬 测试2:1000个线程同时CAS
   成功的线程数: 1
   失败的线程数: 999
   最终值: 101
   结论:1000个线程中只有1个成功!

🔬 测试3:详细分析CAS执行过程
   假设的担心场景:
   1. 线程A读取值5
   2. 线程B也读取值5
   3. 线程A执行CAS(5→6)
   4. 线程B也执行CAS(5→6)
   5. 两个都成功?

   实际情况:
   线程A读取到值: 5
   线程A CAS(5→6)结果: true, 执行后的值: 6
   线程B读取到值: 5
   线程B CAS(5→6)结果: false, 执行后的值: 6

💡 关键理解:
   - 虽然两个线程都读取到了5
   - 但CAS操作是原子的,只有一个能成功
   - 失败的线程会在incrementAndGet()中重试

⚙️ 底层原因

1. CPU硬件保证

LOCK CMPXCHG [memory], new_value
  • LOCK前缀锁定内存总线
  • 其他CPU核心被物理阻塞
  • 一条不可分割的原子指令

2. 时序分析

时间  CPU0              CPU1              内存值
T1    开始CAS           尝试CAS           5
T2    锁定总线          被阻塞等待        5
T3    比较成功          仍在等待          5
T4    写入新值          仍在等待          6
T5    释放锁            开始执行          6
T6    完成              比较失败(65)     6

3. 物理限制

  • 内存总线是单一资源 - 同时只能被一个CPU占用
  • 缓存一致性协议 - 自动使其他CPU缓存失效
  • 硬件级互斥 - 从物理层面杜绝并发

💡 关键理解

CAS vs count++

// count++ (3个操作,可被中断)
int temp = count;    // 1. 读取
temp = temp + 1;     // 2. 计算
count = temp;        // 3. 写入

// CAS (1个原子操作,不可中断)
compareAndSet(expect, update);  // CPU原子指令

AtomicInteger的工作原理

public int incrementAndGet() {
    for (;;) {  // 无限循环直到成功
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next)) {
            return next;  // 成功
        }
        // 失败则重试
    }
}

🎯 核心结论

CAS操作的原子性是由CPU硬件、内存总线、缓存一致性协议等多层机制共同保证的物理事实,两个线程绝对不可能同时成功!

这就是为什么AtomicInteger能解决count++问题的根本原因!