学海无涯,志当存远。燃心砺志,奋进不辍。
愿诸君得此鸡汤,如沐春风,事业有成。
若觉此言甚善,烦请赐赞一枚,共励学途,同铸辉煌!
JMM主要是为了定义多线程环境下变量的访问规则,确保线程之间的通信正确,同时兼顾性能优化。
JMM的作用,主内存和工作内存的抽象,线程间的可见性、有序性、原子性问题。
为什么会有这些抽象,比如CPU缓存导致的可见性问题,指令重排序带来的有序性问题,
以及JMM如何通过happens-before规则解决这些。
JMM的关键点,比如主内存和工作内存的交互操作,如read、load、use、assign、store、write
这些步骤,以及原子性、可见性、有序性这三个特性。
happens-before原则是关键,必须详细说明每个规则,比如程序顺序、锁规则、volatile变量规则、
线程启动和终止规则等,以及这些规则如何帮助开发者避免并发问题。
对并发编程的影响:可见性问题如何通过volatile或synchronized解决,
有序性问题如何通过happens-before或禁止重排序来应对,
原子性问题则需要锁或原子类。
常见的并发工具类如ConcurrentHashMap、AQS等如何利用JMM实现高效线程安全。
结合JMM给出 数据竞争、死锁,或者性能问题解决方案。
比如使用volatile替代锁提升性能,或者正确使用synchronized保证原子性。
可能的误区,比如认为volatile能替代锁,其实只能保证可见性和有序性,不能保证原子性。
或者对happens-before的理解不足,导致代码中出现难以察觉的并发bug。
学习JMM对于掌握其他并发框架或库的基础作用,比如Java并发包中的类大多基于这些规则设计。
实例说明,比如用双重检查锁定单例模式中的volatile应用,或者AtomicInteger的使用例子。
JMM与硬件内存模型的关系,比如缓存一致性协议(如MESI)如何被JMM抽象
总结结构:简介JMM,核心概念(内存抽象、三大特性、happens-before),
对并发编程的影响(可见性、有序性、原子性的解决方案),实际应用中的例子和工具,最后总结重要性
目录
6.2. as-if-serial,happen-before
6.3. volatile不能保证原子性,那如何保证原子性?
Java 内存模型(Java Memory Model, JMM)是 Java 多线程编程的核心规范,它定义了多线程环境下共享变量的访问规则,确保线程间的通信既高效又安全。理解 JMM 是掌握并发编程的关键,因为它解决了可见性、有序性和原子性问题,同时为开发者提供了明确的编程约束和工具。
一、JMM 的核心目标
JMM 的核心是解决多线程环境下的 可见性(Visibility)、有序性(Ordering)和 原子性(Atomicity)问题:
-
可见性:一个线程对共享变量的修改是否能被其他线程及时看到。
-
有序性:程序执行的顺序是否与代码的顺序一致(指令重排序可能破坏有序性)。
-
原子性:操作是否是不可中断的,即要么全部执行成功,要么完全不执行。
二、JMM 的内存抽象模型
JMM 将内存分为 主内存(Main Memory) 和 工作内存(Working Memory):
-
主内存:存储所有共享变量,对所有线程可见。
-
工作内存:每个线程私有的内存区域,存储该线程使用的变量的副本。线程对变量的所有操作(读/写)都必须在工作内存中进行,之后同步到主内存。
关键操作:
-
read
:从主内存读取变量到工作内存。 -
load
:将read
的值放入工作内存的变量副本。 -
use
:线程使用工作内存中的变量值。 -
assign
:线程为工作内存中的变量赋值。 -
store
:将工作内存中的变量值传送到主内存。 -
write
:将store
的值写入主内存的变量。
这些操作需要满足原子性(如 read
和 load
必须连续执行),但多个操作的组合可能被重排序,导致并发问题。
三、JMM 的关键规则:Happens-Before
JMM 通过 Happens-Before 原则 定义操作之间的偏序关系,确保某些操作的执行顺序对开发者可见:
-
程序顺序规则:同一线程内的操作按代码顺序执行(但允许编译器优化重排序)。
-
锁规则:解锁操作 Happens-Before 后续的加锁操作。
-
volatile 规则:对 volatile 变量的写操作 Happens-Before 后续的读操作。
-
线程启动规则:
Thread.start()
Happens-Before 新线程的所有操作。 -
线程终止规则:线程的所有操作 Happens-Before 其他线程检测到该线程终止(如
Thread.join()
)。 -
传递性:若 A Happens-Before B,B Happens-Before C,则 A Happens-Before C。
// volatile 变量的写-读
volatile boolean flag = false;
// 线程 A
flag = true; // 写操作
// 线程 B
if (flag) { // 读操作
// 此处能看到线程 A 对 flag 的修改
}
四、JMM 对并发编程的影响
1. 可见性问题
-
原因:线程修改共享变量后未及时同步到主内存,或未从主内存刷新最新值。
-
解决方案:
-
使用
volatile
关键字:强制变量的读写直接操作主内存,禁止指令重排序。 -
使用
synchronized
或锁:锁的释放会强制同步工作内存到主内存。
-
2. 有序性问题
-
原因:编译器、处理器或 JVM 的指令重排序优化可能导致代码执行顺序与预期不一致。
-
解决方案:
-
volatile
:禁止其前后指令的重排序。 -
synchronized
:通过锁的互斥性保证临界区内的操作有序。 -
final
变量:构造函数中的初始化保证对其他线程可见。
-
3. 原子性问题
-
原因:非原子操作(如
i++
)可能被线程切换打断。 -
解决方案:
-
使用
synchronized
或ReentrantLock
保证代码块原子性。 -
使用原子类(如
AtomicInteger
),基于 CAS(Compare-And-Swap)实现无锁原子操作。
-
五、实践中的应用模式
-
双重检查锁定 DCL(Double-Checked Locking):
class Singleton { private volatile static Singleton instance; public static Singleton getInstance() { if (instance == null) { // 第一次检查 synchronized (Singleton.class) { if (instance == null) { // 第二次检查 instance = new Singleton(); } } } return instance; } }
-
volatile
防止对象初始化时的指令重排序(避免返回未完全初始化的对象)。
-
-
并发容器与工具:
-
ConcurrentHashMap
:通过分段锁和 CAS 提高并发度。 -
CountDownLatch
/CyclicBarrier
:基于volatile
和 AQS(AbstractQueuedSynchronizer)实现线程协调。
-
六、核心面试题
6.1. volatile原理
- 确保变量可见性,立即更新到主内存
- 禁止指令重排,屏障,写前Store读后Load
每个线程都有自己工作内存,会从主内存读取变量的副本进行操作
6.2. as-if-serial,happen-before
as-if-serial
语义是单线程程序执行的基本原则。核心思想:单线程程序的执行结果不能被重排序改变。也就是说,无论编译器和处理器如何优化(如指令重排序),执行结果必须与程序代码的顺序执行结果一致。
happen-before
是多线程程序执行顺序规则。用于描述多线程环境下,操作之间的可见性和顺序性。
- 可见性:如果一个操作 A
happen-before
操作 B,那么操作 A 的结果对操作 B 是可见的。 - 顺序性:
happen-before
关系定义了操作之间的偏序关系,确保某些操作必须按特定顺序执行。
6.3. volatile不能保证原子性,那如何保证原子性?
- synchronized、lock锁
- 使用
java.util.concurrent
下的线程安全集合 或 atomic包中的原子类 - cas,通过硬件指令(如
cmpxchg
)实现原子性 - threadlocal提供变量副本(不能共享数据)
6.4. CAS有哪些问题?如何解决?
硬件级指令,比较两值是否相等,相等更新内存为新值
- ABA问题,cas无法感知【用版本号】
- 长时间自旋,导致资源浪费【根据历史自旋次数动态调整自旋时间;自旋失败后让线程休眠会】
- 只能保证单一变量的原子操作【多个变量的复合操作用lock、synchronized】
- 是非公平的,饥饿问题【java有公平锁】
七、总结
理解 JMM 的要点:
-
可见性:通过
volatile
、锁或final
变量保证。 -
有序性:通过 Happens-Before 规则和内存屏障控制。
-
原子性:通过锁或原子类实现。
JMM 的意义:它不仅是 Java 并发编程的理论基础,更是编写正确、高效多线程代码的指南。开发者需结合 JMM 规则选择合适工具(如 volatile
、锁、原子类),避免因内存可见性、指令重排序等问题导致 Bug。
学海无涯,志当存远。燃心砺志,奋进不辍。
愿诸君得此鸡汤,如沐春风,事业有成。
若觉此言甚善,烦请赐赞一枚,共励学途,同铸辉煌!