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 完成 比较失败(6≠5) 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++问题的根本原因!