JMM专治多线程下变量读写不可见、重排序致逻辑错乱、原子性被破坏三类问题:通过happens-before规则、volatile、synchronized等机制保障可见性、有序性与原子性。

Java内存模型(JMM)不是为了解决“内存不够用”或“GC频繁”这类问题,它专治多线程环境下**变量读写不可见、重排序导致逻辑错乱、以及原子性被破坏**这三类根本性问题。
为什么多个线程读不到彼此写的值?
这是JMM最常被感知到的问题:一个线程更新了sharedFlag = true,另一个线程却一直看到false。根本原因在于,线程可能从自己的工作内存(CPU缓存或寄存器)读取该变量,而没有及时从主内存同步最新值。
解决方式依赖JMM定义的“happens-before”规则和具体同步机制:
-
volatile字段写操作对其他线程立即可见(禁止重排序 + 强制刷新主内存) - 进入
synchronized块前,会清空本地工作内存;退出时强制把变更刷回主内存 - 线程启动(
Thread.start())、终止(Thread.join())也建立happens-before关系
为什么代码顺序没变,执行结果却像“乱序”?
JMM允许编译器、JIT、CPU在不改变单线程语义的前提下重排序指令。例如下面这段代码:
立即学习“Java免费学习笔记(深入)”;
int a = 0;
boolean ready = false;
// 线程1
a = 1;
ready = true;
// 线程2
if (ready) {
System.out.println(a); // 可能输出0!
}
看似a = 1在ready = true之前,但JMM允许将这两句重排序(尤其在无同步时)。线程2看到ready == true,却读到旧的a == 0。
关键约束点:
-
volatile写之前的所有操作,不能被重排序到该写之后 -
volatile读之后的所有操作,不能被重排序到该读之前 -
synchronized块内语句受锁边界限制,不会逸出到外侧
long/double赋值为什么不是原子的?
在32位JVM上,long和double是64位宽,可能被拆成两次32位写操作。若无同步,一个线程可能读到“半个新值+半个旧值”的中间态(如高位是旧值、低位是新值)。
JMM明确要求:volatile long和volatile double的读写必须是原子的;普通long/double则不保证——这不是bug,而是JMM的设计选择,用以平衡性能与可实现性。
实践中更推荐:
- 始终用
volatile修饰共享的long/double字段 - 或封装进
AtomicLong/AtomicLongFieldUpdater等原子类 - 避免依赖非volatile 64位变量的“天然原子性”
真正难的是把happens-before规则映射到具体代码结构里——比如final字段的初始化安全发布、ConcurrentHashMap内部如何用volatile+CAS绕过锁、甚至Lambda闭包捕获变量时的内存可见性边界。这些细节一旦漏掉,问题只会在高并发压测时爆发,且极难复现。










