synchronized通过锁规则建立happens-before关系来间接约束重排序、保障可见性与有序性:线程释放锁的操作happens-before后续线程获取同一锁的操作,从而确保释放前的写操作对获取锁的线程可见,并借助内存屏障防止关键位置重排。

Java中 synchronized 本身不直接“防止指令重排”,而是通过锁规则(Monitor Lock Rule)建立 happens-before 关系,从而间接约束重排序、保障可见性与有序性。
锁规则如何起作用
当一个线程释放锁(unlock),该操作 happens-before 后续任意线程对同一把锁的加锁(lock)操作。这意味着:
- 释放锁前的所有写操作(包括对共享变量的修改),对后续成功获取该锁的线程一定可见;
- 编译器和处理器不能把 unlock 之后的操作重排到 unlock 之前,也不能把 lock 之前的操作重排到 lock 之后——否则会破坏 happens-before 的语义保证;
- 这种约束不是靠禁止所有重排实现的,而是靠内存屏障(memory barrier)在关键点插入指令,强制刷新缓存、同步主内存。
典型代码中的体现
看这段常见示例:
public class Counter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++; // 可能被拆为 read-modify-write 三步
}
}
public int getCount() {
synchronized (lock) {
return count;
}
}
其中:
- 线程A执行
increment()退出synchronized块时,完成 unlock; - 线程B随后进入
getCount()的synchronized块,执行 lock; - 根据锁规则,A的 unlock happens-before B的 lock,因此A对
count的修改对B可见; - 即使A内部有重排序(如先更新其他变量再更新
count),只要在 unlock 前完成,B在 lock 后读到的count就一定是最新值。
它不保证什么
需要特别注意:synchronized 的 happens-before 效果仅发生在跨线程的锁交接点,而不是任意位置:
- 它不保证临界区内部的指令完全不重排(只要不破坏单线程语义,仍可重排);
- 它不提供对临界区外变量的保护——比如在
synchronized块外读写某个 volatile 字段,其顺序需单独依赖 volatile 规则; - 如果两个线程从未在同一个锁上发生 unlock → lock 的接力,就没有 happens-before 关系,也就没有可见性保证。
对比 volatile 的区别
synchronized 和 volatile 都能建立 happens-before,但机制不同:
-
volatile是单变量粒度:写 volatile 变量 happens-before 后续读该变量; -
synchronized是代码块粒度:一次 unlock happens-before 下一次 lock(针对同一把锁),覆盖整个临界区内所有操作; -
synchronized还提供互斥性(原子性),volatile不提供; - 两者都依赖底层内存屏障,但插入位置和强度不同。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











