一、致命幻象:从诡异的可见性问题说起
1.1 幽灵般的数值消失
public class VisibilityDemo { boolean flag = true; int value = 0; void writer() { value = 42; // (1) flag = false; // (2) } void reader() { while (flag); // (3) System.out.println(value); // 可能输出0! } }
现象剖析:即使(1)在(2)之前执行,reader线程可能看到flag=false
但value=0
二、契约基石:JMM内存模型的终极承诺
2.1 硬件现实与Java抽象
处理器缓存
缓存一致性协议
JVM内存模型
重排序规则
开发者
Happens-Before规则
2.2 JMM三大特性
-
原子性:lock/unlock原语保证
-
可见性:Happens-Before规则约束
-
有序性:禁止特定类型重排序
三、九重契约:Happens-Before全景解析
3.1 程序顺序规则(Program Order Rule)
int x = 1; // (1) int y = 2; // (2)
即使发生重排序,单线程视角下(1)总是happens-before(2)
3.2 volatile变量规则(Volatile Variable Rule)
public class VolatileExample { volatile boolean flag = false; int value = 0; void writer() { value = 42; // (1) flag = true; // (2) volatile写 } void reader() { if (flag) { // (3) volatile读 System.out.println(value); // 保证看到42 } } }
四、深度解构:高阶规则全景图
程序顺序规则
传递性规则
锁规则
volatile规则
线程启动规则
线程终结规则
中断规则
对象终结规则
4.1 锁规则的隐藏奥秘
public class LockRuleDemo { private final Object lock = new Object(); private int count = 0; void increment() { synchronized(lock) { // 加锁建立happens-before关系 count++; // 对共享变量的修改 } // 解锁时强制刷新内存 } }
五、终极之战:DCL单例模式与指令重排序
5.1 经典错误实现
public class Singleton { private static Singleton instance; public static Singleton getInstance() { if (instance == null) { // (1) synchronized (Singleton.class) { // (2) if (instance == null) { // (3) instance = new Singleton(); // (4) } } } return instance; } }
致命缺陷:对象的初始化可能被重排序,导致其他线程获得未完全初始化的实例
5.2 正确实现方案
public class SafeSingleton { // 添加volatile防止指令重排序 private volatile static SafeSingleton instance; public static SafeSingleton getInstance() { if (instance == null) { synchronized (SafeInstance.class) { if (instance == null) { instance = new SafeSingleton(); // 写操作与volatile写建立happens-before } } } return instance; } }
六、契约封印:Happens-Before八大应用案例
6.1 Thread.start()规则
public class StartRuleDemo { static int data = 0; public static void main(String[] args) { Thread t = new Thread(() -> { // 保证能看到主线程对data的修改 System.out.println(data); }); data = 42; // (1) happens-before线程启动(2) t.start(); // (2) } }
6.2 Thread.join()规则
public class JoinRuleDemo { static int result; public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { result = calculate(); // (1) }); t.start(); t.join(); // (2) 保证(1) happens-before这里 System.out.println(result); // 正确获取结果 } }
七、血泪教训:Happens-Before七大误区
7.1 时间顺序≠Happens-Before
int x = 1; // (A) int y = 2; // (B)
即使B在A之后执行,没有数据依赖则不保证可见性
7.2 单线程不适用?
public class SingleThreadHappensBefore { int a = 0; boolean flag = false; void write() { a = 1; // (1) flag = true; // (2) } void read() { if (flag) { // (3) int i = a; // (4) 单线程没问题 } } } // 但多线程可能看到(2)先于(1)
八、剑指核心:Happens-Before与内存屏障
8.1 JVM级别的屏障类型
屏障类型 | 作用描述 | 对应场景 |
---|---|---|
LoadLoad屏障 | 禁止读操作重排序 | volatile读之后 |
StoreStore屏障 | 禁止写操作重排序 | volatile写之前 |
LoadStore屏障 | 禁止读后写重排序 | volatile读之后 |
StoreLoad屏障 | 禁止写后读重排序(全能屏障) | volatile写之后 |
8.2 volatile的真实工作原理
public class VolatileBarrier { volatile int v = 0; void write() { v = 1; // 插入StoreStore屏障 + StoreLoad屏障 } void read() { int tmp = v; // 插入LoadLoad屏障 + LoadStore屏障 } }
九、实战演练:Arthas诊断可见性问题
9.1 使用watch命令追踪变量
watch com.example.ConcurrentDemo value
9.2 查看内存屏障效果
// 原始代码 public class VisibilityTest { int a = 0; volatile int b = 0; void write() { a = 1; // (1) b = 2; // (2) } } // 通过Arthas的jad反编译查看内存屏障 monitor com.example.VisibilityTest write
十、契约永生:构建安全的并发系统
10.1 安全发布对象的四大法则
-
静态初始化器
-
volatile修饰
-
final字段
-
正确构造的不可变对象
10.2 Happens-Before检查清单
- 共享变量是否使用volatile或原子类
- 同步块是否覆盖所有竞态操作
- 是否存在隐式依赖的非happens-before关系
- 是否误用线程不安全对象发布
- 锁范围是否正确划定临界区