文章目录Java并发编程JMM Java内存模型 系统性知识体系一、JMM 基础概念与核心目标1.1 什么是JMM1.2 JMM的核心目标二、JMM的内存结构2.1 主内存与工作内存2.2 内存交互操作三、JMM的三大核心特性3.1 原子性(Atomicity)3.1.1 Java中的原子操作3.1.2 非原子操作示例3.1.3 原子性的保证方式3.2 可见性(Visibility)3.2.1 可见性问题的根源3.2.2 可见性的保证方式3.3 有序性(Ordering)3.3.1 重排序问题3.3.2 有序性的保证方式四、重排序与内存屏障4.1 数据依赖性4.2 控制依赖性4.3 内存屏障(Memory Barrier)五、happens-before原则核心5.1 什么是happens-before5.2 八大happens-before规则5.3 happens-before与JMM的关系六、volatile关键字详解6.1 volatile的特性6.2 volatile的内存语义6.3 volatile的实现原理6.4 volatile的使用场景七、synchronized关键字详解7.1 synchronized的特性7.2 synchronized的内存语义7.3 synchronized的实现原理八、final关键字的内存语义8.1 final的特性8.2 final的内存语义8.3 final的使用注意事项九、常见问题与误区9.1 常见误区9.2 常见问题十、知识体系总结Java并发编程JMM内存模型 面试高频考点清单一、基础概念类必问二、三大核心特性必问区分基础与进阶三、重排序与内存屏障高频进阶四、happens-before原则核心中的核心必问五、volatile关键字最高频考点必问六、synchronized关键字最高频考点必问七、final关键字的内存语义中频八、经典应用场景与问题必问九、面试答题高分技巧Java并发编程JMM Java内存模型 系统性知识体系一、JMM 基础概念与核心目标1.1 什么是JMMJava内存模型(Java Memory Model, JMM)是Java虚拟机规范中定义的一种抽象模型它规定了线程如何通过内存进行交互解决了多线程环境下的内存可见性、原子性和有序性问题确保Java程序在不同硬件架构和操作系统下的并发行为一致性。JMM不是物理内存模型而是抽象的内存访问规范它屏蔽了不同硬件和操作系统的内存访问差异让Java程序在各种平台上都能表现出一致的并发行为。1.2 JMM的核心目标解决并发安全问题确保多线程程序在并发执行时的正确性提供内存可见性保证一个线程对共享变量的修改能被其他线程及时看到规范指令执行顺序防止编译器和处理器的重排序破坏程序语义平衡性能与安全在保证并发安全的前提下尽可能提高程序执行效率二、JMM的内存结构2.1 主内存与工作内存JMM将内存划分为两个主要区域内存区域存储内容访问特性对应硬件主内存(Main Memory)所有共享变量实例字段、静态字段、数组元素所有线程共享访问速度较慢对应物理内存的RAM工作内存(Working Memory)线程使用的共享变量的副本线程私有访问速度极快对应CPU的寄存器和高速缓存2.2 内存交互操作JMM定义了8种原子操作来完成主内存与工作内存之间的交互lock(锁定)作用于主内存变量将变量标记为线程独占状态unlock(解锁)作用于主内存变量释放被锁定的变量read(读取)作用于主内存变量将变量值从主内存传输到工作内存load(载入)作用于工作内存变量将read操作得到的值放入工作内存的变量副本中use(使用)作用于工作内存变量将变量值传递给执行引擎assign(赋值)作用于工作内存变量将执行引擎返回的值赋给工作内存变量store(存储)作用于工作内存变量将变量值从工作内存传输到主内存write(写入)作用于主内存变量将store操作得到的值写入主内存变量交互规则read和load、store和write必须成对出现不允许线程丢弃最近的assign操作变量修改后必须同步回主内存不允许线程将未发生assign操作的变量从工作内存同步回主内存新变量只能在主内存中诞生不允许在工作内存中直接使用未初始化的变量三、JMM的三大核心特性3.1 原子性(Atomicity)定义一个操作是不可分割的要么全部执行成功要么全部不执行执行过程中不会被其他线程中断。3.1.1 Java中的原子操作基本类型的读取和赋值除了long和double类型的64位操作在32位JVM上可能被拆分为两个32位操作所有引用类型的读取和赋值java.util.concurrent.atomic包中的原子类操作AtomicInteger、AtomicLong等3.1.2 非原子操作示例// 非原子操作包含读取-修改-写入三个步骤intcount0;count;// 等价于int temp count; temp temp 1; count temp;3.1.3 原子性的保证方式synchronized关键字通过锁机制保证同一时刻只有一个线程执行临界区代码Lock接口提供比synchronized更灵活的锁机制原子类基于CAS(Compare-And-Swap)操作实现无锁原子性3.2 可见性(Visibility)定义当一个线程修改了共享变量的值其他线程能够立即看到这个修改。3.2.1 可见性问题的根源线程修改的是工作内存中的变量副本而不是主内存中的原始变量线程不会立即将修改后的变量同步回主内存其他线程不会立即从主内存重新读取最新的变量值3.2.2 可见性的保证方式volatile关键字强制将修改立即同步回主内存并强制其他线程从主内存重新读取变量synchronized关键字在释放锁时将工作内存中的所有修改同步回主内存Lock接口与synchronized类似在释放锁时保证可见性final关键字final字段在构造函数中初始化完成后对所有线程可见3.3 有序性(Ordering)定义程序执行的顺序按照代码的先后顺序执行。3.3.1 重排序问题为了提高性能编译器和处理器会对指令进行重排序编译器重排序编译器在不改变单线程程序语义的前提下重新安排语句的执行顺序指令级并行重排序处理器将多条指令重叠执行内存系统重排序处理器使用读写缓冲区使得加载和存储操作看起来是乱序执行的重排序的原则as-if-serial语义不管怎么重排序单线程程序的执行结果不能被改变编译器和处理器不会对存在数据依赖关系的操作进行重排序3.3.2 有序性的保证方式volatile关键字禁止指令重排序synchronized关键字保证同一时刻只有一个线程执行临界区代码相当于让临界区代码串行执行happens-before原则JMM提供的最基本的有序性保证四、重排序与内存屏障4.1 数据依赖性如果两个操作访问同一个变量且其中一个操作是写操作那么这两个操作之间就存在数据依赖性。数据依赖性分为三种写后读(RAW)写一个变量之后再读这个变量写后写(WAW)写一个变量之后再写这个变量读后写(WAR)读一个变量之后再写这个变量编译器和处理器不会改变存在数据依赖性的两个操作的执行顺序。4.2 控制依赖性如果一个操作的执行依赖于另一个条件判断操作的结果那么这两个操作之间就存在控制依赖性。注意编译器和处理器会对存在控制依赖性的操作进行重排序这可能导致多线程程序出现问题。4.3 内存屏障(Memory Barrier)内存屏障是一组处理器指令用于控制特定操作的执行顺序和内存可见性。JMM通过内存屏障来禁止特定类型的重排序。JMM定义了四种内存屏障LoadLoad屏障禁止前面的读操作与后面的读操作重排序StoreStore屏障禁止前面的写操作与后面的写操作重排序LoadStore屏障禁止前面的读操作与后面的写操作重排序StoreLoad屏障禁止前面的写操作与后面的读操作重排序最耗时具有全能性五、happens-before原则核心5.1 什么是happens-beforehappens-before是JMM中定义的两个操作之间的偏序关系。如果操作A happens-before 操作B那么操作A的执行结果对操作B可见操作A的执行顺序排在操作B之前注意happens-before关系并不意味着操作A必须在操作B之前执行只要操作A的结果对操作B可见并且操作A的执行顺序看起来在操作B之前即可。5.2 八大happens-before规则程序顺序规则一个线程中的每个操作happens-before于该线程中的任意后续操作监视器锁规则对一个锁的解锁操作happens-before于随后对这个锁的加锁操作volatile变量规则对一个volatile变量的写操作happens-before于随后对这个变量的读操作线程启动规则Thread对象的start()方法调用happens-before于该线程中的任意操作线程终止规则线程中的所有操作happens-before于其他线程检测到该线程已经终止线程中断规则对线程interrupt()方法的调用happens-before于被中断线程检测到中断事件对象终结规则一个对象的初始化完成happens-before于该对象的finalize()方法开始执行传递性规则如果A happens-before B且B happens-before C那么A happens-before C5.3 happens-before与JMM的关系JMM通过happens-before原则向程序员提供了内存可见性保证。如果两个操作之间没有happens-before关系那么JMM对它们的执行顺序和可见性没有任何保证。六、volatile关键字详解6.1 volatile的特性可见性对volatile变量的写操作立即对所有线程可见禁止重排序volatile变量的读写操作不会被重排序不保证原子性volatile不能保证复合操作的原子性如count6.2 volatile的内存语义volatile写的内存语义当写一个volatile变量时JMM会把该线程工作内存中的所有共享变量刷新到主内存volatile读的内存语义当读一个volatile变量时JMM会把该线程工作内存中的所有共享变量置为无效然后从主内存中重新读取6.3 volatile的实现原理volatile通过在编译时插入内存屏障来实现其特性在每个volatile写操作前插入StoreStore屏障在每个volatile写操作后插入StoreLoad屏障在每个volatile读操作后插入LoadLoad屏障在每个volatile读操作后插入LoadStore屏障6.4 volatile的使用场景状态标记如boolean flag true/false双重检查锁定(DCL)实现单例模式一次性安全发布如懒加载的单例对象七、synchronized关键字详解7.1 synchronized的特性原子性保证临界区代码的原子性执行可见性在释放锁时将工作内存中的所有修改同步回主内存有序性保证临界区代码的串行执行可重入性同一个线程可以多次获取同一个锁7.2 synchronized的内存语义加锁的内存语义当线程获取锁时JMM会把该线程工作内存中的所有共享变量置为无效然后从主内存中重新读取释放锁的内存语义当线程释放锁时JMM会把该线程工作内存中的所有共享变量刷新到主内存7.3 synchronized的实现原理synchronized基于对象头中的Mark Word和监视器锁(Monitor)实现偏向锁当只有一个线程访问同步块时使用偏向锁几乎没有性能开销轻量级锁当有多个线程交替访问同步块时使用轻量级锁基于CAS操作重量级锁当有多个线程同时竞争锁时膨胀为重量级锁基于操作系统的互斥量八、final关键字的内存语义8.1 final的特性不可变性final字段一旦被初始化就不能被修改可见性final字段在构造函数中初始化完成后对所有线程可见8.2 final的内存语义写final字段的重排序规则禁止把final字段的写操作重排序到构造函数之外读final字段的重排序规则禁止把初次读对象引用与初次读该对象包含的final字段重排序8.3 final的使用注意事项final字段必须在声明时或构造函数中初始化不要在构造函数中让this引用逸出final引用类型的字段其引用的对象的内容仍然可以被修改九、常见问题与误区9.1 常见误区误区volatile能保证原子性纠正volatile只能保证可见性和有序性不能保证复合操作的原子性误区synchronized只能保证原子性纠正synchronized同时保证原子性、可见性和有序性误区只要使用了volatile或synchronized程序就一定是线程安全的纠正需要正确使用这些关键字并且考虑所有可能的并发问题误区重排序是有害的应该完全禁止纠正重排序是编译器和处理器为了提高性能而进行的优化只要正确使用同步机制就不会影响程序的正确性9.2 常见问题问题为什么DCL单例模式需要使用volatile答案防止指令重排序导致其他线程获取到未初始化完成的对象问题long和double类型的变量为什么不是原子操作答案在32位JVM上64位的long和double类型的读写操作会被拆分为两个32位的操作因此不是原子的问题happens-before原则和as-if-serial语义有什么关系答案as-if-serial语义保证单线程程序的执行结果不变happens-before原则保证多线程程序的执行结果不变十、知识体系总结JMM是Java并发编程的基础它通过定义主内存与工作内存的交互规则、三大核心特性原子性、可见性、有序性以及happens-before原则解决了多线程环境下的内存访问问题。原子性由synchronized、Lock和原子类保证可见性由volatile、synchronized、Lock和final保证有序性由volatile、synchronized和happens-before原则保证理解JMM是编写正确、高效的Java并发程序的关键。在实际开发中我们应该根据具体的业务场景选择合适的同步机制来保证并发安全。Java并发编程JMM内存模型 面试高频考点清单按考察频率重要性排序标注星级附核心答题要点与面试官追问方向一、基础概念类必问考点考察频率核心答题要点面试官常追问1. 什么是JMM它的核心目标是什么★★★★★① JMM是Java虚拟机规范定义的抽象内存访问模型② 屏蔽不同硬件/OS的内存访问差异保证跨平台并发一致性③ 核心解决多线程环境下的原子性、可见性、有序性三大问题④ 平衡并发安全与程序执行性能为什么需要JMM没有JMM会出现什么问题2. 主内存与工作内存的区别★★★★★① 主内存存储所有共享变量实例字段、静态字段、数组线程共享访问慢对应物理RAM② 工作内存存储共享变量的副本线程私有访问极快对应CPU寄存器和高速缓存③ 线程对变量的所有操作都必须在工作内存中进行不能直接读写主内存为什么不直接操作主内存3. JMM定义的8种内存交互操作★★★☆☆按顺序lock→read→load→use→assign→store→write→unlock核心规则read/load、store/write必须成对出现修改必须同步回主存为什么需要这8种操作可以简化吗二、三大核心特性必问区分基础与进阶考点考察频率核心答题要点面试官常追问1. 什么是原子性、可见性、有序性分别如何保证★★★★★①原子性操作不可分割要么全执行要么全不执行保证方式synchronized、Lock、Atomic原子类②可见性一个线程的修改能被其他线程立即看到保证方式volatile、synchronized、Lock、final③有序性程序执行顺序与代码逻辑顺序一致保证方式volatile、synchronized、happens-before原则这三个特性之间有什么关系缺少一个会导致什么问题2. 为什么long和double类型的读写不是原子操作★★★★☆① 32位JVM上64位的long/double读写会被拆分为两个32位操作② 可能出现字撕裂问题一个线程写了高32位另一个线程读到了低32位③ 64位JVM上通常保证原子性但规范不强制要求如何解决long/double的原子性问题3. as-if-serial语义是什么★★★★☆① 编译器和处理器在不改变单线程程序执行结果的前提下可以对指令进行重排序② 是单线程程序看起来顺序执行的保证③ 只保证单线程语义不保证多线程语义as-if-serial和happens-before的关系三、重排序与内存屏障高频进阶考点考察频率核心答题要点面试官常追问1. 什么是重排序有哪些类型★★★★★① 重排序是编译器和处理器为了提高性能对指令执行顺序的优化② 三种类型编译器重排序、指令级并行重排序、内存系统重排序③ 重排序可能破坏多线程程序的语义重排序在什么情况下是安全的2. 什么是内存屏障JMM有哪几种★★★★★① 内存屏障是处理器指令用于控制特定操作的执行顺序和内存可见性② 四种类型- LoadLoad禁止前面读与后面读重排序- StoreStore禁止前面写与后面写重排序- LoadStore禁止前面读与后面写重排序- StoreLoad禁止前面写与后面读重排序最耗时全能型为什么StoreLoad屏障最耗时3. 数据依赖性与控制依赖性的区别★★★☆☆① 数据依赖两个操作访问同一变量且有一个是写操作写后读、写后写、读后写编译器和处理器不会重排有数据依赖的操作② 控制依赖操作执行依赖于条件判断结果编译器和处理器会重排有控制依赖的操作可能导致多线程问题举一个控制依赖导致并发问题的例子四、happens-before原则核心中的核心必问考点考察频率核心答题要点面试官常追问1. 什么是happens-before原则它的作用是什么★★★★★① happens-before是JMM定义的两个操作之间的偏序关系② 如果A happens-before B则- A的执行结果对B可见- A的执行顺序排在B之前③ 作用向程序员提供明确的内存可见性保证是判断多线程程序是否安全的唯一依据happens-before关系意味着A必须在B之前物理执行吗2. 列举happens-before的八大规则★★★★★按重要性排序1. 程序顺序规则线程内操作按代码顺序2. 监视器锁规则解锁happens-before于随后的加锁3. volatile变量规则写happens-before于随后的读4. 传递性规则A→B且B→C则A→C5. 线程启动规则start() happens-before于线程内所有操作6. 线程终止规则线程内所有操作happens-before于其他线程检测到其终止7. 线程中断规则interrupt() happens-before于检测中断8. 对象终结规则初始化完成happens-before于finalize()用happens-before规则分析一个具体的并发场景3. happens-before与as-if-serial的关系★★★★☆① as-if-serial保证单线程程序的执行结果不变② happens-before保证多线程程序的执行结果不变③ 两者都是为了在不改变程序执行结果的前提下尽可能提高程序执行效率没有happens-before关系的两个操作JMM会如何处理五、volatile关键字最高频考点必问考点考察频率核心答题要点面试官常追问1. volatile的三大特性★★★★★①保证可见性写操作立即刷新到主存读操作直接从主存读取②禁止指令重排序通过内存屏障实现③不保证原子性不能保证复合操作如count的原子性为什么volatile不能保证原子性2. volatile的内存语义★★★★★①写语义写volatile变量时将该线程工作内存中所有共享变量刷新到主内存②读语义读volatile变量时将该线程工作内存中所有共享变量置为无效从主存重新读取为什么volatile写会刷新所有共享变量3. volatile的实现原理内存屏障★★★★★编译时在volatile操作前后插入内存屏障① 写前StoreStore屏障禁止前面写与volatile写重排② 写后StoreLoad屏障禁止volatile写与后面读重排③ 读后LoadLoad屏障禁止volatile读与后面读重排④ 读后LoadStore屏障禁止volatile读与后面写重排不同处理器的内存屏障实现有差异吗4. volatile的使用场景★★★★★①状态标记如boolean flag true/false一写多读②双重检查锁定(DCL)实现单例模式③一次性安全发布如懒加载的单例对象④volatile变量作为刷新触发器什么情况下不能使用volatile5. volatile与synchronized的区别★★★★★volatile是更轻量级的同步机制性能更好六、synchronized关键字最高频考点必问考点考察频率核心答题要点面试官常追问1. synchronized的四大特性★★★★★①原子性保证临界区代码串行执行②可见性释放锁时刷新所有修改到主存③有序性临界区代码串行执行相当于禁止重排序④可重入性同一线程可以多次获取同一个锁为什么synchronized是可重入的2. synchronized的内存语义★★★★☆①加锁语义获取锁时将线程工作内存中所有共享变量置为无效从主存重新读取②释放锁语义释放锁时将线程工作内存中所有共享变量刷新到主内存synchronized的内存语义和volatile有什么异同3. synchronized的实现原理★★★★★① 基于对象头中的Mark Word和**监视器锁(Monitor)**实现② 每个对象都可以作为锁锁信息存储在对象头中③ 底层通过monitorenter和monitorexit指令实现对象头的结构是什么样的4. synchronized的锁升级过程重点★★★★★锁只能升级不能降级①无锁对象刚创建时②偏向锁只有一个线程访问时将线程ID记录在对象头中几乎无性能开销③轻量级锁多个线程交替访问时通过CAS操作竞争锁不阻塞线程④重量级锁多个线程同时竞争时膨胀为操作系统互斥量线程阻塞每种锁的适用场景和性能特点5. synchronized与Lock的区别★★★★☆Lock提供了更灵活的锁机制如可中断锁、超时锁、公平锁等七、final关键字的内存语义中频考点考察频率核心答题要点面试官常追问1. final的内存语义★★★☆☆①写final字段禁止将final字段的写操作重排序到构造函数之外②读final字段禁止将初次读对象引用与初次读该对象的final字段重排序为什么final字段在构造函数初始化后对所有线程可见2. 使用final的注意事项★★★☆☆① final字段必须在声明时或构造函数中初始化② 不要在构造函数中让this引用逸出③ final引用类型字段其引用的对象内容仍然可以被修改如何实现真正的不可变对象八、经典应用场景与问题必问考点考察频率核心答题要点面试官常追问1. 为什么DCL单例模式需要使用volatile★★★★★① 防止指令重排序导致其他线程获取到未初始化完成的对象② 对象创建过程分配内存→初始化对象→将引用指向内存地址③ 重排序可能导致顺序变为分配内存→将引用指向内存地址→初始化对象④ 此时其他线程可能获取到一个半初始化的对象不使用volatile会出现什么问题有其他实现线程安全单例的方式吗2. 如何实现一个线程安全的单例模式★★★★★推荐三种方式① 饿汉式静态常量② 双重检查锁定(DCL)volatile③ 静态内部类推荐懒加载且线程安全④ 枚举单例最安全防止反射和序列化破坏各种实现方式的优缺点3. 什么是伪共享如何解决★★★☆☆① 伪共享多个线程修改同一个缓存行中的不同变量导致缓存行频繁失效② 解决方法- 使用Contended注解JDK 8- 填充字段使每个变量独占一个缓存行伪共享对性能的影响有多大九、面试答题高分技巧通用答题思路先定义→再讲原理→最后讲应用场景和注意事项突出深度回答时不仅要说出是什么还要说出为什么和怎么做结合实例用具体的代码例子说明问题比如用count说明原子性问题主动引导回答完一个问题后可以主动说我还可以讲一下XXX相关的内容展示你的知识广度注意区分明确区分JMM的抽象模型和实际的硬件内存模型
【Java并发编程】JMM Java内存模型:原子性、可见性、有序性、happens-before原则(附《思维导图》+《面试高频考点清单》)
发布时间:2026/5/23 16:40:11
文章目录Java并发编程JMM Java内存模型 系统性知识体系一、JMM 基础概念与核心目标1.1 什么是JMM1.2 JMM的核心目标二、JMM的内存结构2.1 主内存与工作内存2.2 内存交互操作三、JMM的三大核心特性3.1 原子性(Atomicity)3.1.1 Java中的原子操作3.1.2 非原子操作示例3.1.3 原子性的保证方式3.2 可见性(Visibility)3.2.1 可见性问题的根源3.2.2 可见性的保证方式3.3 有序性(Ordering)3.3.1 重排序问题3.3.2 有序性的保证方式四、重排序与内存屏障4.1 数据依赖性4.2 控制依赖性4.3 内存屏障(Memory Barrier)五、happens-before原则核心5.1 什么是happens-before5.2 八大happens-before规则5.3 happens-before与JMM的关系六、volatile关键字详解6.1 volatile的特性6.2 volatile的内存语义6.3 volatile的实现原理6.4 volatile的使用场景七、synchronized关键字详解7.1 synchronized的特性7.2 synchronized的内存语义7.3 synchronized的实现原理八、final关键字的内存语义8.1 final的特性8.2 final的内存语义8.3 final的使用注意事项九、常见问题与误区9.1 常见误区9.2 常见问题十、知识体系总结Java并发编程JMM内存模型 面试高频考点清单一、基础概念类必问二、三大核心特性必问区分基础与进阶三、重排序与内存屏障高频进阶四、happens-before原则核心中的核心必问五、volatile关键字最高频考点必问六、synchronized关键字最高频考点必问七、final关键字的内存语义中频八、经典应用场景与问题必问九、面试答题高分技巧Java并发编程JMM Java内存模型 系统性知识体系一、JMM 基础概念与核心目标1.1 什么是JMMJava内存模型(Java Memory Model, JMM)是Java虚拟机规范中定义的一种抽象模型它规定了线程如何通过内存进行交互解决了多线程环境下的内存可见性、原子性和有序性问题确保Java程序在不同硬件架构和操作系统下的并发行为一致性。JMM不是物理内存模型而是抽象的内存访问规范它屏蔽了不同硬件和操作系统的内存访问差异让Java程序在各种平台上都能表现出一致的并发行为。1.2 JMM的核心目标解决并发安全问题确保多线程程序在并发执行时的正确性提供内存可见性保证一个线程对共享变量的修改能被其他线程及时看到规范指令执行顺序防止编译器和处理器的重排序破坏程序语义平衡性能与安全在保证并发安全的前提下尽可能提高程序执行效率二、JMM的内存结构2.1 主内存与工作内存JMM将内存划分为两个主要区域内存区域存储内容访问特性对应硬件主内存(Main Memory)所有共享变量实例字段、静态字段、数组元素所有线程共享访问速度较慢对应物理内存的RAM工作内存(Working Memory)线程使用的共享变量的副本线程私有访问速度极快对应CPU的寄存器和高速缓存2.2 内存交互操作JMM定义了8种原子操作来完成主内存与工作内存之间的交互lock(锁定)作用于主内存变量将变量标记为线程独占状态unlock(解锁)作用于主内存变量释放被锁定的变量read(读取)作用于主内存变量将变量值从主内存传输到工作内存load(载入)作用于工作内存变量将read操作得到的值放入工作内存的变量副本中use(使用)作用于工作内存变量将变量值传递给执行引擎assign(赋值)作用于工作内存变量将执行引擎返回的值赋给工作内存变量store(存储)作用于工作内存变量将变量值从工作内存传输到主内存write(写入)作用于主内存变量将store操作得到的值写入主内存变量交互规则read和load、store和write必须成对出现不允许线程丢弃最近的assign操作变量修改后必须同步回主内存不允许线程将未发生assign操作的变量从工作内存同步回主内存新变量只能在主内存中诞生不允许在工作内存中直接使用未初始化的变量三、JMM的三大核心特性3.1 原子性(Atomicity)定义一个操作是不可分割的要么全部执行成功要么全部不执行执行过程中不会被其他线程中断。3.1.1 Java中的原子操作基本类型的读取和赋值除了long和double类型的64位操作在32位JVM上可能被拆分为两个32位操作所有引用类型的读取和赋值java.util.concurrent.atomic包中的原子类操作AtomicInteger、AtomicLong等3.1.2 非原子操作示例// 非原子操作包含读取-修改-写入三个步骤intcount0;count;// 等价于int temp count; temp temp 1; count temp;3.1.3 原子性的保证方式synchronized关键字通过锁机制保证同一时刻只有一个线程执行临界区代码Lock接口提供比synchronized更灵活的锁机制原子类基于CAS(Compare-And-Swap)操作实现无锁原子性3.2 可见性(Visibility)定义当一个线程修改了共享变量的值其他线程能够立即看到这个修改。3.2.1 可见性问题的根源线程修改的是工作内存中的变量副本而不是主内存中的原始变量线程不会立即将修改后的变量同步回主内存其他线程不会立即从主内存重新读取最新的变量值3.2.2 可见性的保证方式volatile关键字强制将修改立即同步回主内存并强制其他线程从主内存重新读取变量synchronized关键字在释放锁时将工作内存中的所有修改同步回主内存Lock接口与synchronized类似在释放锁时保证可见性final关键字final字段在构造函数中初始化完成后对所有线程可见3.3 有序性(Ordering)定义程序执行的顺序按照代码的先后顺序执行。3.3.1 重排序问题为了提高性能编译器和处理器会对指令进行重排序编译器重排序编译器在不改变单线程程序语义的前提下重新安排语句的执行顺序指令级并行重排序处理器将多条指令重叠执行内存系统重排序处理器使用读写缓冲区使得加载和存储操作看起来是乱序执行的重排序的原则as-if-serial语义不管怎么重排序单线程程序的执行结果不能被改变编译器和处理器不会对存在数据依赖关系的操作进行重排序3.3.2 有序性的保证方式volatile关键字禁止指令重排序synchronized关键字保证同一时刻只有一个线程执行临界区代码相当于让临界区代码串行执行happens-before原则JMM提供的最基本的有序性保证四、重排序与内存屏障4.1 数据依赖性如果两个操作访问同一个变量且其中一个操作是写操作那么这两个操作之间就存在数据依赖性。数据依赖性分为三种写后读(RAW)写一个变量之后再读这个变量写后写(WAW)写一个变量之后再写这个变量读后写(WAR)读一个变量之后再写这个变量编译器和处理器不会改变存在数据依赖性的两个操作的执行顺序。4.2 控制依赖性如果一个操作的执行依赖于另一个条件判断操作的结果那么这两个操作之间就存在控制依赖性。注意编译器和处理器会对存在控制依赖性的操作进行重排序这可能导致多线程程序出现问题。4.3 内存屏障(Memory Barrier)内存屏障是一组处理器指令用于控制特定操作的执行顺序和内存可见性。JMM通过内存屏障来禁止特定类型的重排序。JMM定义了四种内存屏障LoadLoad屏障禁止前面的读操作与后面的读操作重排序StoreStore屏障禁止前面的写操作与后面的写操作重排序LoadStore屏障禁止前面的读操作与后面的写操作重排序StoreLoad屏障禁止前面的写操作与后面的读操作重排序最耗时具有全能性五、happens-before原则核心5.1 什么是happens-beforehappens-before是JMM中定义的两个操作之间的偏序关系。如果操作A happens-before 操作B那么操作A的执行结果对操作B可见操作A的执行顺序排在操作B之前注意happens-before关系并不意味着操作A必须在操作B之前执行只要操作A的结果对操作B可见并且操作A的执行顺序看起来在操作B之前即可。5.2 八大happens-before规则程序顺序规则一个线程中的每个操作happens-before于该线程中的任意后续操作监视器锁规则对一个锁的解锁操作happens-before于随后对这个锁的加锁操作volatile变量规则对一个volatile变量的写操作happens-before于随后对这个变量的读操作线程启动规则Thread对象的start()方法调用happens-before于该线程中的任意操作线程终止规则线程中的所有操作happens-before于其他线程检测到该线程已经终止线程中断规则对线程interrupt()方法的调用happens-before于被中断线程检测到中断事件对象终结规则一个对象的初始化完成happens-before于该对象的finalize()方法开始执行传递性规则如果A happens-before B且B happens-before C那么A happens-before C5.3 happens-before与JMM的关系JMM通过happens-before原则向程序员提供了内存可见性保证。如果两个操作之间没有happens-before关系那么JMM对它们的执行顺序和可见性没有任何保证。六、volatile关键字详解6.1 volatile的特性可见性对volatile变量的写操作立即对所有线程可见禁止重排序volatile变量的读写操作不会被重排序不保证原子性volatile不能保证复合操作的原子性如count6.2 volatile的内存语义volatile写的内存语义当写一个volatile变量时JMM会把该线程工作内存中的所有共享变量刷新到主内存volatile读的内存语义当读一个volatile变量时JMM会把该线程工作内存中的所有共享变量置为无效然后从主内存中重新读取6.3 volatile的实现原理volatile通过在编译时插入内存屏障来实现其特性在每个volatile写操作前插入StoreStore屏障在每个volatile写操作后插入StoreLoad屏障在每个volatile读操作后插入LoadLoad屏障在每个volatile读操作后插入LoadStore屏障6.4 volatile的使用场景状态标记如boolean flag true/false双重检查锁定(DCL)实现单例模式一次性安全发布如懒加载的单例对象七、synchronized关键字详解7.1 synchronized的特性原子性保证临界区代码的原子性执行可见性在释放锁时将工作内存中的所有修改同步回主内存有序性保证临界区代码的串行执行可重入性同一个线程可以多次获取同一个锁7.2 synchronized的内存语义加锁的内存语义当线程获取锁时JMM会把该线程工作内存中的所有共享变量置为无效然后从主内存中重新读取释放锁的内存语义当线程释放锁时JMM会把该线程工作内存中的所有共享变量刷新到主内存7.3 synchronized的实现原理synchronized基于对象头中的Mark Word和监视器锁(Monitor)实现偏向锁当只有一个线程访问同步块时使用偏向锁几乎没有性能开销轻量级锁当有多个线程交替访问同步块时使用轻量级锁基于CAS操作重量级锁当有多个线程同时竞争锁时膨胀为重量级锁基于操作系统的互斥量八、final关键字的内存语义8.1 final的特性不可变性final字段一旦被初始化就不能被修改可见性final字段在构造函数中初始化完成后对所有线程可见8.2 final的内存语义写final字段的重排序规则禁止把final字段的写操作重排序到构造函数之外读final字段的重排序规则禁止把初次读对象引用与初次读该对象包含的final字段重排序8.3 final的使用注意事项final字段必须在声明时或构造函数中初始化不要在构造函数中让this引用逸出final引用类型的字段其引用的对象的内容仍然可以被修改九、常见问题与误区9.1 常见误区误区volatile能保证原子性纠正volatile只能保证可见性和有序性不能保证复合操作的原子性误区synchronized只能保证原子性纠正synchronized同时保证原子性、可见性和有序性误区只要使用了volatile或synchronized程序就一定是线程安全的纠正需要正确使用这些关键字并且考虑所有可能的并发问题误区重排序是有害的应该完全禁止纠正重排序是编译器和处理器为了提高性能而进行的优化只要正确使用同步机制就不会影响程序的正确性9.2 常见问题问题为什么DCL单例模式需要使用volatile答案防止指令重排序导致其他线程获取到未初始化完成的对象问题long和double类型的变量为什么不是原子操作答案在32位JVM上64位的long和double类型的读写操作会被拆分为两个32位的操作因此不是原子的问题happens-before原则和as-if-serial语义有什么关系答案as-if-serial语义保证单线程程序的执行结果不变happens-before原则保证多线程程序的执行结果不变十、知识体系总结JMM是Java并发编程的基础它通过定义主内存与工作内存的交互规则、三大核心特性原子性、可见性、有序性以及happens-before原则解决了多线程环境下的内存访问问题。原子性由synchronized、Lock和原子类保证可见性由volatile、synchronized、Lock和final保证有序性由volatile、synchronized和happens-before原则保证理解JMM是编写正确、高效的Java并发程序的关键。在实际开发中我们应该根据具体的业务场景选择合适的同步机制来保证并发安全。Java并发编程JMM内存模型 面试高频考点清单按考察频率重要性排序标注星级附核心答题要点与面试官追问方向一、基础概念类必问考点考察频率核心答题要点面试官常追问1. 什么是JMM它的核心目标是什么★★★★★① JMM是Java虚拟机规范定义的抽象内存访问模型② 屏蔽不同硬件/OS的内存访问差异保证跨平台并发一致性③ 核心解决多线程环境下的原子性、可见性、有序性三大问题④ 平衡并发安全与程序执行性能为什么需要JMM没有JMM会出现什么问题2. 主内存与工作内存的区别★★★★★① 主内存存储所有共享变量实例字段、静态字段、数组线程共享访问慢对应物理RAM② 工作内存存储共享变量的副本线程私有访问极快对应CPU寄存器和高速缓存③ 线程对变量的所有操作都必须在工作内存中进行不能直接读写主内存为什么不直接操作主内存3. JMM定义的8种内存交互操作★★★☆☆按顺序lock→read→load→use→assign→store→write→unlock核心规则read/load、store/write必须成对出现修改必须同步回主存为什么需要这8种操作可以简化吗二、三大核心特性必问区分基础与进阶考点考察频率核心答题要点面试官常追问1. 什么是原子性、可见性、有序性分别如何保证★★★★★①原子性操作不可分割要么全执行要么全不执行保证方式synchronized、Lock、Atomic原子类②可见性一个线程的修改能被其他线程立即看到保证方式volatile、synchronized、Lock、final③有序性程序执行顺序与代码逻辑顺序一致保证方式volatile、synchronized、happens-before原则这三个特性之间有什么关系缺少一个会导致什么问题2. 为什么long和double类型的读写不是原子操作★★★★☆① 32位JVM上64位的long/double读写会被拆分为两个32位操作② 可能出现字撕裂问题一个线程写了高32位另一个线程读到了低32位③ 64位JVM上通常保证原子性但规范不强制要求如何解决long/double的原子性问题3. as-if-serial语义是什么★★★★☆① 编译器和处理器在不改变单线程程序执行结果的前提下可以对指令进行重排序② 是单线程程序看起来顺序执行的保证③ 只保证单线程语义不保证多线程语义as-if-serial和happens-before的关系三、重排序与内存屏障高频进阶考点考察频率核心答题要点面试官常追问1. 什么是重排序有哪些类型★★★★★① 重排序是编译器和处理器为了提高性能对指令执行顺序的优化② 三种类型编译器重排序、指令级并行重排序、内存系统重排序③ 重排序可能破坏多线程程序的语义重排序在什么情况下是安全的2. 什么是内存屏障JMM有哪几种★★★★★① 内存屏障是处理器指令用于控制特定操作的执行顺序和内存可见性② 四种类型- LoadLoad禁止前面读与后面读重排序- StoreStore禁止前面写与后面写重排序- LoadStore禁止前面读与后面写重排序- StoreLoad禁止前面写与后面读重排序最耗时全能型为什么StoreLoad屏障最耗时3. 数据依赖性与控制依赖性的区别★★★☆☆① 数据依赖两个操作访问同一变量且有一个是写操作写后读、写后写、读后写编译器和处理器不会重排有数据依赖的操作② 控制依赖操作执行依赖于条件判断结果编译器和处理器会重排有控制依赖的操作可能导致多线程问题举一个控制依赖导致并发问题的例子四、happens-before原则核心中的核心必问考点考察频率核心答题要点面试官常追问1. 什么是happens-before原则它的作用是什么★★★★★① happens-before是JMM定义的两个操作之间的偏序关系② 如果A happens-before B则- A的执行结果对B可见- A的执行顺序排在B之前③ 作用向程序员提供明确的内存可见性保证是判断多线程程序是否安全的唯一依据happens-before关系意味着A必须在B之前物理执行吗2. 列举happens-before的八大规则★★★★★按重要性排序1. 程序顺序规则线程内操作按代码顺序2. 监视器锁规则解锁happens-before于随后的加锁3. volatile变量规则写happens-before于随后的读4. 传递性规则A→B且B→C则A→C5. 线程启动规则start() happens-before于线程内所有操作6. 线程终止规则线程内所有操作happens-before于其他线程检测到其终止7. 线程中断规则interrupt() happens-before于检测中断8. 对象终结规则初始化完成happens-before于finalize()用happens-before规则分析一个具体的并发场景3. happens-before与as-if-serial的关系★★★★☆① as-if-serial保证单线程程序的执行结果不变② happens-before保证多线程程序的执行结果不变③ 两者都是为了在不改变程序执行结果的前提下尽可能提高程序执行效率没有happens-before关系的两个操作JMM会如何处理五、volatile关键字最高频考点必问考点考察频率核心答题要点面试官常追问1. volatile的三大特性★★★★★①保证可见性写操作立即刷新到主存读操作直接从主存读取②禁止指令重排序通过内存屏障实现③不保证原子性不能保证复合操作如count的原子性为什么volatile不能保证原子性2. volatile的内存语义★★★★★①写语义写volatile变量时将该线程工作内存中所有共享变量刷新到主内存②读语义读volatile变量时将该线程工作内存中所有共享变量置为无效从主存重新读取为什么volatile写会刷新所有共享变量3. volatile的实现原理内存屏障★★★★★编译时在volatile操作前后插入内存屏障① 写前StoreStore屏障禁止前面写与volatile写重排② 写后StoreLoad屏障禁止volatile写与后面读重排③ 读后LoadLoad屏障禁止volatile读与后面读重排④ 读后LoadStore屏障禁止volatile读与后面写重排不同处理器的内存屏障实现有差异吗4. volatile的使用场景★★★★★①状态标记如boolean flag true/false一写多读②双重检查锁定(DCL)实现单例模式③一次性安全发布如懒加载的单例对象④volatile变量作为刷新触发器什么情况下不能使用volatile5. volatile与synchronized的区别★★★★★volatile是更轻量级的同步机制性能更好六、synchronized关键字最高频考点必问考点考察频率核心答题要点面试官常追问1. synchronized的四大特性★★★★★①原子性保证临界区代码串行执行②可见性释放锁时刷新所有修改到主存③有序性临界区代码串行执行相当于禁止重排序④可重入性同一线程可以多次获取同一个锁为什么synchronized是可重入的2. synchronized的内存语义★★★★☆①加锁语义获取锁时将线程工作内存中所有共享变量置为无效从主存重新读取②释放锁语义释放锁时将线程工作内存中所有共享变量刷新到主内存synchronized的内存语义和volatile有什么异同3. synchronized的实现原理★★★★★① 基于对象头中的Mark Word和**监视器锁(Monitor)**实现② 每个对象都可以作为锁锁信息存储在对象头中③ 底层通过monitorenter和monitorexit指令实现对象头的结构是什么样的4. synchronized的锁升级过程重点★★★★★锁只能升级不能降级①无锁对象刚创建时②偏向锁只有一个线程访问时将线程ID记录在对象头中几乎无性能开销③轻量级锁多个线程交替访问时通过CAS操作竞争锁不阻塞线程④重量级锁多个线程同时竞争时膨胀为操作系统互斥量线程阻塞每种锁的适用场景和性能特点5. synchronized与Lock的区别★★★★☆Lock提供了更灵活的锁机制如可中断锁、超时锁、公平锁等七、final关键字的内存语义中频考点考察频率核心答题要点面试官常追问1. final的内存语义★★★☆☆①写final字段禁止将final字段的写操作重排序到构造函数之外②读final字段禁止将初次读对象引用与初次读该对象的final字段重排序为什么final字段在构造函数初始化后对所有线程可见2. 使用final的注意事项★★★☆☆① final字段必须在声明时或构造函数中初始化② 不要在构造函数中让this引用逸出③ final引用类型字段其引用的对象内容仍然可以被修改如何实现真正的不可变对象八、经典应用场景与问题必问考点考察频率核心答题要点面试官常追问1. 为什么DCL单例模式需要使用volatile★★★★★① 防止指令重排序导致其他线程获取到未初始化完成的对象② 对象创建过程分配内存→初始化对象→将引用指向内存地址③ 重排序可能导致顺序变为分配内存→将引用指向内存地址→初始化对象④ 此时其他线程可能获取到一个半初始化的对象不使用volatile会出现什么问题有其他实现线程安全单例的方式吗2. 如何实现一个线程安全的单例模式★★★★★推荐三种方式① 饿汉式静态常量② 双重检查锁定(DCL)volatile③ 静态内部类推荐懒加载且线程安全④ 枚举单例最安全防止反射和序列化破坏各种实现方式的优缺点3. 什么是伪共享如何解决★★★☆☆① 伪共享多个线程修改同一个缓存行中的不同变量导致缓存行频繁失效② 解决方法- 使用Contended注解JDK 8- 填充字段使每个变量独占一个缓存行伪共享对性能的影响有多大九、面试答题高分技巧通用答题思路先定义→再讲原理→最后讲应用场景和注意事项突出深度回答时不仅要说出是什么还要说出为什么和怎么做结合实例用具体的代码例子说明问题比如用count说明原子性问题主动引导回答完一个问题后可以主动说我还可以讲一下XXX相关的内容展示你的知识广度注意区分明确区分JMM的抽象模型和实际的硬件内存模型