synchronized解锁后其他线程能看见最新值,是因为JMM规定unlock时强制将本线程对锁内变量的修改写回主内存,lock时强制从主内存读取最新值,且该可见性仅在同一线程使用同一把锁的加锁/解锁序列间生效。

为什么 synchronized 解锁后,其他线程加锁就能看到最新值?
因为 synchronized 不只是“上锁”,它强制触发一次「工作内存 → 主内存」的写回(unlock 时),以及一次「主内存 → 工作内存」的读取(lock 时)。这不是 Java 虚拟机的额外功能,而是 JMM(Java 内存模型)对 synchronized 的语义约束:每次解锁前,所有本线程对该锁保护范围内变量的修改,都必须刷新到主内存;每次加锁后,本线程会重新从主内存加载这些变量的最新值。
synchronized 的可见性传播不是自动“广播”,而是按锁粒度生效
常见误解是“只要用了 synchronized,所有变量就立刻可见”。错。可见性只在**同一把锁**的加锁/解锁序列之间传播:
- 线程 A 持有
obj1锁并修改了flag和data,然后释放锁 → 这两个变量的最新值会写入主内存 - 线程 B 随后也用
synchronized(obj1)加锁 → 它能 100% 看到flag和data的新值 - 但如果线程 C 用的是
synchronized(obj2),哪怕它也读flag,也无法保证看到线程 A 写的值 —— 锁不同,JMM 不保证跨锁可见性
volatile 和 synchronized 在可见性上的关键区别
volatile 是单变量级的轻量同步:对一个 volatile 变量的写,happens-before 于后续任意线程对该变量的读;但不保证复合操作(如 count++)原子性,也不影响其他非 volatile 变量。
synchronized 是块级的强同步:不仅保证锁内所有共享变量的可见性传播,还天然提供原子性和互斥。但它有开销,且容易因锁竞争阻塞线程。
举个典型坑:private static volatile boolean flag = true;private static int data = 0;
如果线程 A 写 data = 42; flag = false;,线程 B 看到 flag == false,却不一定看到 data == 42 —— 因为 data 不是 volatile,JMM 不保证它和 flag 的写顺序对其他线程可见。这时必须用 synchronized 包裹二者,或让 data 也声明为 volatile(仅适用于简单赋值场景)。
别指望“sleep + volatile”替代真正的同步机制
有人试过这样“绕过”问题:while (flag) { Thread.sleep(1); }
表面看它“最终”能退出,但这不是靠可见性保障,而是靠概率和调度延迟。它既浪费 CPU(唤醒开销)、又不可靠(极端情况下仍可能卡死),更破坏了程序语义 —— 你真正需要的不是“等一会儿再看”,而是“确保我看到你写完的结果”。
真正该做的,是明确哪些变量需跨线程通信、哪些操作需原子执行,然后选对工具:
- 单变量状态开关 → volatile
- 多变量协同更新 / 读写混合逻辑 → synchronized 或 ReentrantLock
- 计数器类场景 → AtomicInteger 等原子类(底层仍是 volatile + CAS)
最常被忽略的一点:可见性从来不是孤立问题。它总和原子性、重排序交织在一起。只加 volatile 却不考虑操作是否可分割,或者只用 synchronized 却锁错了对象,结果都一样 —— 看似跑通,实则埋雷。










