synchronized不禁止同步块内指令重排,仅通过happens-before规则和边界内存屏障(入口loadload/loadstore、出口storestore/storeload)保障跨线程有序性与可见性。

synchronized 并不保证同步块内部指令“完全不被重排序”,它只在关键边界施加约束,确保多线程间的行为符合预期。真正起作用的是 Java 内存模型(JMM)定义的 happens-before 规则 和配套的 内存屏障,而不是禁止所有重排。
同步块入口和出口插入内存屏障
当线程进入或退出 synchronized 块时,JVM 会在对应位置插入特定类型的内存屏障:
- 进入前:插入 LoadLoad 和 LoadStore 屏障,防止上面的读写操作被重排到同步块内
- 退出后:插入 StoreStore 和 StoreLoad 屏障,防止下面的读写操作被重排到同步块内
- 整体效果接近一个“全屏障”(Full Memory Barrier),但仅作用于边界,不干预块内逻辑
依靠 happens-before 建立跨线程顺序保障
有序性保障不是靠“锁住每条语句”,而是靠锁交接点建立明确的先后关系:
- 线程 A 执行完 synchronized 块并释放锁(unlock)
- 线程 B 随后获取同一把锁(lock)
- 根据 JMM 锁规则:A 的 unlock happens-before B 的 lock
- 因此,A 在释放锁前对共享变量的所有修改,对 B 获取锁后的读取都可见且有序
同步块内部仍允许重排序
只要不破坏单线程语义,JVM 和 CPU 仍可能对同步块内的指令重排:
- 例如:
synchronized(lock) { a = 1; b = 2; }可能实际执行为先写 b 再写 a - 这对单线程结果无影响(a 和 b 最终都是确定值)
- 但若其他线程依赖 a 和 b 的写入时序(比如通过 flag 判断),就可能出现问题——这正是 DCL 单例中仍需 volatile 的原因
它不提供什么
理解它的边界,才能避免误用:
- 不保护同步块外的变量读写顺序,那些需靠 volatile 或其他机制单独约束
- 不同线程若没发生锁的传递(即没有 unlock → lock 的接力),就没有 happens-before 关系
- 不能替代 volatile 解决对象构造过程中的指令重排(如 new 对象三步:分配内存→调构造→赋引用)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











