Happens-Before的九重契约

一、致命幻象:从诡异的可见性问题说起

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=falsevalue=0


二、契约基石:JMM内存模型的终极承诺

2.1 硬件现实与Java抽象

 

处理器缓存

缓存一致性协议

JVM内存模型

重排序规则

开发者

Happens-Before规则

2.2 JMM三大特性

  1. 原子性:lock/unlock原语保证

  2. 可见性:Happens-Before规则约束

  3. 有序性:禁止特定类型重排序


三、九重契约: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 安全发布对象的四大法则

  1. 静态初始化器

  2. volatile修饰

  3. final字段

  4. 正确构造的不可变对象

10.2 Happens-Before检查清单

  • 共享变量是否使用volatile或原子类
  • 同步块是否覆盖所有竞态操作
  • 是否存在隐式依赖的非happens-before关系
  • 是否误用线程不安全对象发布
  • 锁范围是否正确划定临界区
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值