一、致命幻象:从诡异的可见性问题说起
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关系
- 是否误用线程不安全对象发布
- 锁范围是否正确划定临界区

被折叠的 条评论
为什么被折叠?



