unlock() 与后续同一锁的 lock() 存在 happens-before 关系,而非任意 unlock 与 lock;其可见性依赖传递性链:写操作→unlock→lock→读操作,且须满足同锁、时序正确、写在临界区内三前提。

unlock() 和 lock() 之间没有直接 happens-before 关系
很多人误以为 unlock() 本身“天然地” happens-before 后续的 lock(),这是错的。JMM 并不规定“任意 unlock 都 happens-before 任意 lock”。真正成立的是:**对同一个锁对象的 unlock() happens-before 后续对该同一锁对象的 lock()** —— 关键在“同一个锁”和“后续”(时间上发生在其后)。
传递性才是同步生效的核心机制
假设线程 A 执行完临界区并释放锁,线程 B 随后获取该锁并进入临界区。要让线程 B 看到线程 A 对共享变量的修改,靠的不是 unlock→lock 这一对动作的直接关系,而是两条独立的 happens-before 链通过传递性拼接:
- 线程 A 中,
x = 42(写共享变量) happens-beforeunlock()(程序次序规则) -
unlock()(线程 A) happens-beforelock()(线程 B,同一锁,且时间上在其后)(监视器锁规则) - 于是由传递性得:
x = 42happens-beforelock()(线程 B) - 而
lock()happens-before 线程 B 中临界区内的任意操作(比如System.out.println(x))(程序次序规则) - 最终:
x = 42happens-beforeSystem.out.println(x)→ 线程 B 一定能读到 42
容易忽略的关键约束
这条链能成立,依赖三个不可妥协的前提:
- 必须是**同一个锁对象**:用
new Object()分别 new 出两个锁,unlock()和lock()就无法建立 happens-before - 必须存在**实际的执行时序**:
unlock()真的在lock()之前完成;如果线程 B 在 A 还没 unlock 前就抢到了锁(比如锁被重入或有竞争失败重试),链就断了 - 共享变量必须在**临界区内修改**:如果
x = 42写在synchronized块外面,它和unlock()之间就没有程序次序保障,整条链失效
volatile + 程序次序也能构造等效链,但语义不同
不用锁,也能靠 volatile 和传递性达成类似效果,例如:
static volatile boolean ready = false;
static int data = 0;
<p>// 线程 A
data = 42; // A1
ready = true; // A2 → happens-before 后续所有对 ready 的读</p><p>// 线程 B
while (!ready) {} // B1 → A2 happens-before B1(volatile 规则)
System.out.println(data); // B2 → B1 happens-before B2(程序次序)
// 由传递性:A1 happens-before B2 → data 可见
这里没用锁,但依然靠两段 happens-before(A1→A2、A2→B1、B1→B2)串起完整可见性链。区别在于:锁提供互斥+可见性,volatile 只提供可见性+禁止重排序,不保证原子性。
真正难的不是记住八条规则,而是每次写并发逻辑时,都得亲手画出那条从写到读的 happens-before 链——漏掉任一环,就可能在某个 CPU 缓存层级上看到过期值。

















