发布于2026-07-05 阅读(0)
扫一扫,手机访问
先下一个明确的结论:volatile 只能保证单个读或写操作的原子性,而复合操作(比如 count++ 这种“读—改—写”)必须依赖 synchronized 或原子类才能避免竞态条件。 这个区别是并发编程里最容易踩的坑之一,很多人误以为加了 volatile 就万事大吉,结果线上出了 bug 还一头雾水。下面拆开细说。

volatile 保证的只是单次读或写操作的原子性——比如 flag = true 这样的赋值,或者 if (done) 这样的简单判断。但像 count++、value += 1、list.add(x) 这类操作,在 JVM 层面会被拆成多个指令:先读取当前值,然后在寄存器里计算,最后把新值写回去。volatile 虽然能确保每次读或写本身不被重排序、对其他线程立即可见,但它管不了这三步之间其他线程插进来的情况——结果就是“丢失更新”。
举个例子,两个线程同时执行 volatile int count++,可能两个线程都读到初始值 0,各自加 1 后写回,最后 count 只变成 1,而不是预期的 2。这就是典型的竞态条件,volatile 对此无能为力。
synchronized 的思路完全不同:它通过互斥锁把一段代码逻辑整体拧成“不可分割的临界区”。只要操作被包在 synchronized 块或方法里,JVM 就保证同一时刻最多只有一个线程能进入该区域。还是 count++ 的例子,放到 synchronized 方法中,整个“锁定→读→加 1→写→解锁”流程由单一线程独占完成,其他线程只能在外面等着,自然就不会出现交错执行的问题。
说白了,volatile 只防“打断读”或“打断写”,但防不住“读和写之间被打断”;而 synchronized 直接让整个操作序列变成一个整体,谁也插不进来。
下面这些情况,必须用 synchronized(或者 AtomicInteger 等原子类),千万别指望 volatile:
i++、sum += x,看似简单,实则是三步骤。ArrayList.add()、HashMap.put(),内部不止一次内存操作,还可能触发扩容。balance 和 lastTransactionTime 要同步更新,volatile 管不了“同时改两组数据”的原子性。怎么判断?一个简单的标准:只要操作涉及“不止一次内存访问”,并且中间状态可能被其他线程看到或影响逻辑,那就不是 volatile 能兜住的。别图省事,该上锁就上锁。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8