1. JMM 是什么JMM 全称是Java Memory Model中文叫Java 内存模型。它不是在讲某一块真实硬件内存也不是直接在讲 CPU 的 L1、L2、L3 缓存。它是一套 Java 多线程规范用来规定1. 一个线程修改变量后其他线程什么时候能看到 2. 编译器和 CPU 能不能调整代码执行顺序 3. 哪些操作天然安全哪些操作需要 volatile、锁或 Atomic 类一句话JMM 是 Java 对多线程读写共享变量的一套抽象规则。2. 为什么需要 JMMJava 程序运行时会有很多优化编译器可能重排序指令JIT 可能把变量缓存到寄存器CPU 会用 L1、L2、L3 缓存CPU 也可能乱序执行一个线程改了变量另一个线程不一定马上看到。例如classDemo{privatebooleanrunningtrue;publicvoidloop(){while(running){// do something}}publicvoidstop(){runningfalse;}}你以为一个线程调用stop()后另一个线程里的loop()会退出。但如果running不是volatile循环线程可能一直读到旧值true导致停不下来。这就是可见性问题。3. JMM 的两个抽象概念JMM 抽象出两个概念主内存 Main Memory 工作内存 Working Memory可以画成这样主内存 所有线程共享变量的抽象存储区 / \ / \ 工作内存 A 工作内存 B Thread A Thread B线程操作共享变量时抽象流程是1. 从主内存读取变量 2. 拷贝到自己的工作内存 3. 在线程中使用、修改 4. 再写回主内存注意主内存和工作内存都是 JMM 的抽象不是硬件里固定的某一块内存。4. 主内存是不是物理内存 RAM不是完全等于。可以粗略理解成主内存 ≈ Java 中所有线程共享变量的最终存储位置但 JMM 不关心变量实际在 RAM、CPU Cache 还是寄存器里。JMM 只关心线程 A 写了变量线程 B 什么时候能看到5. 工作内存是不是 L1、L2、L3不是。工作内存是 Java 层面的抽象不等于某一级 CPU 缓存。它底层可能对应CPU 寄存器 L1 Cache L2 Cache L3 Cache Store Buffer 写缓冲 Load Buffer 读缓冲 JIT 优化出来的临时副本 线程栈上的局部副本所以不能说工作内存 L1 工作内存 L2 工作内存 L3更准确地说工作内存代表线程可能持有的共享变量副本底层可能落在寄存器、CPU 缓存、写缓冲区等地方。6. CPU 缓存大概是什么硬件上通常有寄存器最快容量最小 L1 Cache通常每个 CPU 核心私有 L2 Cache通常每个 CPU 核心私有容量比 L1 大 L3 Cache通常多个核心共享 RAM物理内存最慢大致访问层级寄存器 - L1 - L2 - L3 - RAM但 JMM 不直接规定使用哪一级缓存。7. volatile 是什么volatile是 Java 的关键字主要保证两件事1. 可见性 2. 禁止特定重排序示例classDemo{privatevolatilebooleanrunningtrue;publicvoidloop(){while(running){// do something}}publicvoidstop(){runningfalse;}}加了volatile后一个线程执行runningfalse;另一个线程就能看到这个变化。8. volatile 是不是让线程不用缓存不是。这个说法不准确volatile 让线程不用自己的缓存直接读主存更准确的说法是volatile 通过内存屏障和 CPU 缓存一致性机制保证变量读写的可见性和有序性但不会真正关闭 CPU 缓存。CPU 仍然会使用 L1、L2、L3。9. volatile 的可见性privatevolatileintflag0;线程 Aflag1;线程 Bintxflag;JMM 保证线程 B 能看到线程 A 对 flag 的写入。更专业地说对 volatile 变量的写happens-before 后续任意线程对同一个 volatile 变量的读。10. volatile 的有序性示例classDemo{privateintdata0;privatevolatilebooleanreadyfalse;publicvoidwriter(){data100;readytrue;}publicvoidreader(){if(ready){System.out.println(data);}}}如果 reader 线程看到readytrue那么它也能看到data100原因是data 100 不能被重排序到 ready true 之后 ready true 是 volatile 写 reader 读到 ready true 后可以看到 volatile 写之前的普通写11. volatile 不能保证原子性volatile不能保证复合操作原子性。volatileintcount0;count;count实际是三步1. 读 count 2. 加 1 3. 写回 count多个线程同时执行还是可能丢数据。正确方式AtomicIntegercountnewAtomicInteger();count.incrementAndGet();或者synchronized12. happens-before 是 JMM 的核心JMM 最重要的概念是happens-before如果操作 A happens-before 操作 B那么1. A 的结果对 B 可见 2. A 的执行顺序排在 B 之前13. 常见 happens-before 规则13.1 程序顺序规则同一个线程内前面的操作 happens-before 后面的操作。inta1;intb2;在当前线程里a 1 happens-before b 213.2 volatile 规则对 volatile 变量的写happens-before 后续对这个变量的读。13.3 锁规则一个线程释放锁happens-before 另一个线程获取同一把锁。synchronized(lock){value10;}13.4 线程启动规则调用Thread.start()前的操作对新线程可见。13.5 线程 join 规则线程中的操作 happens-before 其他线程从join()返回。14. synchronized 和 volatile 的区别能力volatilesynchronized可见性有有有序性有有原子性不保证复合操作保证同步块互斥是否阻塞不阻塞可能阻塞适合场景状态标志、单次发布多变量一致性、临界区15. AtomicInteger 和 volatile 的区别volatileintcount;只能保证读写可见不能保证count线程安全。AtomicIntegercountnewAtomicInteger();可以保证count.incrementAndGet();是原子操作。16. 双重检查锁为什么要 volatile经典单例classSingleton{privatestaticvolatileSingletoninstance;publicstaticSingletongetInstance(){if(instancenull){synchronized(Singleton.class){if(instancenull){instancenewSingleton();}}}returninstance;}}为什么instance要加volatile因为instancenewSingleton();可能被拆成1. 分配内存 2. 初始化对象 3. 把对象地址赋给 instance如果发生重排序可能变成1. 分配内存 2. 把对象地址赋给 instance 3. 初始化对象另一个线程看到instance ! null但对象还没初始化完。volatile可以禁止这种危险重排序。17. 常见误区误区 1volatile 会禁用 CPU 缓存错误。volatile不会禁用 CPU 缓存。它是通过内存屏障和缓存一致性协议保证可见性。误区 2工作内存就是 L2 或 L3错误。JMM 的工作内存是抽象概念可能对应寄存器、L1、L2、L3、写缓冲、JIT 临时副本等。误区 3volatile 可以替代 synchronized不完全可以。volatile不能保证复合操作原子性也不能保护多个变量的一致性。误区 4volatile 修饰集合就线程安全错误。volatileListStringlistnewArrayList();这里只保证list这个引用的可见性不保证ArrayList内部操作线程安全。18. 速查表场景推荐方式一个线程通知另一个线程停止volatile boolean简单状态发布volatile自增计数AtomicInteger/LongAdder多变量一致更新synchronized/Lock保护临界区synchronized/Lock高并发计数LongAdder单例双重检查volatilesynchronized19. 总结JMM 是 Java 多线程的内存可见性规则。它抽象出主内存 工作内存但这两个概念不等于具体硬件主内存 ≠ 单纯物理 RAM 工作内存 ≠ 固定的 L1/L2/L3volatile的作用是1. 保证可见性 2. 禁止特定重排序但它不能保证复合操作原子性 集合线程安全 多个变量的一致性一句话volatile 不是让线程不用缓存而是让 JVM 和 CPU 按照 JMM 的规则保证变量读写的可见性和有序性。