避坑指南:在Ubuntu 20.04上编译安装GTSAM 4.2并运行因子图示例
2026/4/12 1:52:09
count++看起来是一行代码,但实际对应3个CPU指令:
// Java代码count++;// 实际执行的CPU指令:1.读取count的当前值到CPU寄存器(read)2.把寄存器的值加1(add)3.把新值写回内存(write)假设volatile int count = 0;
时间线 | 线程A的操作 | 线程B的操作 | 内存中count的值 ------|--------------------------|--------------------------|---------------- 1 | read: count=0 → 寄存器A=0 | | 0 2 | | read: count=0 → 寄存器B=0 | 0 3 | add: 寄存器A=0+1=1 | | 0 4 | | add: 寄存器B=0+1=1 | 0 5 | write: 写回count=1 | | 1 6 | | write: 写回count=1 | 1(应该是2!)结果:两个线程都做了+1操作,但最终结果是1而不是2。
volatile只保证了:
但问题是:线程B在第2步已经读过了(读取的是0),不会再重新读!
线程A: 读count(0) → 计算1 → 写count(1,立即刷新) ↑ ↓ 内存count: 0 ← 冲突 → 1 ↑ ↓ 线程B: 读count(0) → 计算1 → 写count(1,覆盖了A的结果)// 线程A(写线程)publicvoidwrite(){flag=true;// 单个写操作data=100;}// 线程B、C、D(读线程)publicvoidread(){if(flag){// 一定能看到trueSystem.out.println(data);// 一定能看到100}}// 线程A、B、C都执行这个:publicvoidincrement(){count++;// 问题在这里!这个操作是"读-改-写"三部曲// 相当于:// int temp = count; // 1.读(可能读到旧值)// temp = temp + 1; // 2.改(在各自线程中改)// count = temp; // 3.写(会相互覆盖)}想象一个共享的记事本(内存)和三个人(线程):
结果:卖了2件商品,库存应该是8,但实际是9 ❌
AtomicIntegercount=newAtomicInteger(0);// count.incrementAndGet() 内部原理:publicfinalintincrementAndGet(){intprev,next;do{prev=get();// 1.读取当前值next=prev+1;// 2.计算新值}while(!compareAndSet(prev,next));// 3.比较并交换returnnext;}关键在第3步:compareAndSet(prev, next)意思是:
用记事本的例子:
总之,关键是要认识到count++不是一步操作,而是三步,而volatile只保证了每一步内部的内存可见性,但没有保证这三步作为一个整体的原子性。