synchronized通过happens-before规则和内存屏障约束重排序,而非完全禁止:解锁happens-before后续加锁,进入/退出同步块时插入相应内存屏障,仅保障边界有序性,不干预临界区内部重排。

synchronized 并不直接“禁止”所有指令重排,而是通过建立 happens-before 关系和插入内存屏障,在关键边界上约束重排序,从而保障多线程下的有序性与可见性。
锁规则构建 happens-before 边界
当一个线程释放锁(monitorexit),该操作 happens-before 后续任意线程对同一把锁的获取(monitorenter)。这意味着:
- 释放锁前的所有写操作,对后续成功加锁的线程一定可见;
- 编译器和 CPU 不能把 unlock 之后的指令重排到 unlock 之前;
- 也不能把 lock 之前的指令重排到 lock 之后;
- 这种顺序保证不是靠冻结所有重排,而是靠语义契约 + 底层内存屏障实现。
内存屏障在同步块边界起作用
synchronized 在进入和退出临界区时,自动插入特定类型的内存屏障:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- 进入同步块(monitorenter)时:插入 LoadLoad 和 LoadStore 屏障,防止上面的读/写被重排到临界区内部;
- 退出同步块(monitorexit)时:插入 StoreStore 和 StoreLoad 屏障,防止临界区内的写被重排到后面,也防止后面的读被提前到临界区内;
- 这些屏障不干预临界区内部的重排(只要不破坏单线程语义),只守住“门框”位置。
它能管住什么、管不住什么
synchronized 的重排约束有明确范围:
立即学习“Java免费学习笔记(深入)”;
- ✅ 跨线程的锁交接点(unlock → lock)之间,确保操作可见且顺序可靠;
- ✅ 同步块外的读写不会“插队”进临界区,也不会让临界区操作“溜出去”;
- ❌ 不保证临界区内部指令完全按源码顺序执行(例如两个无依赖的赋值仍可能重排);
- ❌ 对未受保护的变量(如 synchronized 块外的 volatile 字段)不提供顺序保证,需单独依赖对应规则。
典型场景中的实际效果
以双重检查锁定(DCL)为例:
- new Singleton() 包含三步:分配内存 → 初始化对象 → 赋值引用;
- 若仅靠 synchronized 包裹初始化逻辑,虽能阻止外部线程看到部分构造的对象,但无法完全杜绝构造过程中的 2→3 重排;
- 因此仍需配合 volatile 修饰 instance,才能彻底禁止该重排——说明 synchronized 的有序性是强但非万能的。

















