03.可见性、有序性与原子性问题
总结
- 并发 Bug 三个根源:可见性(CPU 缓存不共享)、原子性(线程切换)、有序性(指令重排)
- 安全性靠加锁,活跃性(死锁/活锁/饥饿)靠设计,性能靠细粒度锁和无锁算法
- JMM 通过 volatile/synchronized/final 和 happens-before 规则按需禁用这些优化
并发 Bug 的三个根源,本质都是 CPU/编译器为了提升性能做的优化副作用。Java 通过 04.Java内存模型JMM 来按需禁用这些优化。
1. 三个问题
可见性:一个线程对共享变量的修改,另一个线程不能立刻看到。
- 原因:多核 CPU 各自有缓存,缓存不共享。
原子性:一个或多个操作在 CPU 执行过程中不被中断。
- 原因:线程切换。CPU 只保证指令级原子,不保证高级语言操作符级别(如
i++是三条指令)。 - 解决方案见 05.原子性解决方案-互斥锁。
有序性:程序不一定按代码顺序执行。
- 原因:编译器优化会重排指令。
- 典型案例:双重检查单例,
new操作可能先赋地址再初始化,另一个线程拿到未初始化的对象。