Java内存模型
Java内存模型(JMM)是Java虚拟机规范中定义的一种抽象内存模型,它规定了多线程环境下,线程如何与主内存及工作内存交互,以及线程间共享变量的可见性、有序性和原子性规则。其核心目标是解决多线程并发中的内存可见性、指令重排序等问题,为开发者提供一套可预测的并发编程语义。
一、JMM的核心组件
主内存(Main Memory)
- 存储所有共享变量(实例字段、静态字段等)。
- 所有线程均可访问,但线程不能直接操作主内存中的变量。
工作内存(Working Memory)
- 每个线程独享的私有内存区域。
- 存储该线程使用的共享变量的副本。
- 线程对变量的所有操作(读/写)必须在工作内存中进行,不能直接读写主内存。
二、JMM的关键规则
内存交互操作
JMM定义了8种原子操作(已简化,实际由JVM实现):lock
:锁定主内存变量,标识为线程独占。unlock
:解锁主内存变量。read
:从主内存读取变量到工作内存。load
:将read
得到的值放入工作内存副本。use
:将工作内存变量值传递给执行引擎(如CPU)。assign
:将执行引擎结果赋给工作内存变量。store
:将工作内存变量值传送到主内存。write
:将store
传递的值写入主内存变量。
操作约束:
read
和load
、store
和write
必须按顺序成对出现。- 变量必须从主内存
read
后,才能被use
;必须被assign
后,才能store
回主内存。
可见性规则
- 线程修改共享变量后,必须同步回主内存。
- 其他线程需从主内存重新读取该变量才能看到最新值。
- 关键字保障(如
volatile
):强制每次访问变量都从主内存读写。
有序性规则(禁止指令重排序)
- 编译器和处理器会对指令进行重排序优化。
- JMM通过
happens-before
原则(见下文)定义操作间的顺序约束。
三、happens-before 原则
JMM通过happens-before
(先行发生)规则定义操作间的可见性与顺序性,无需开发者手动同步。部分规则:
- 程序顺序规则:同一线程内的操作,按代码顺序执行。
- volatile规则:
volatile
变量的写操作happens-before
后续的读操作。 - 锁规则(监视器锁):解锁操作
happens-before
后续的加锁操作。 - 传递性:若A
happens-before
B,Bhappens-before
C,则Ahappens-before
C。 - 线程启动规则:
Thread.start()
前的操作happens-before
新线程的任何操作。 - 线程终止规则:线程中的所有操作
happens-before
其他线程检测到该线程终止。
✅ 示例:
1
2
3
4
5
6
7
8
9
10
11 int x = 0;
volatile boolean v = false;
// 线程A
x = 42; // (1)
v = true; // (2) volatile写
// 线程B
if (v) { // (3) volatile读
System.out.println(x); // (4) 保证看到x=42
}根据
happens-before
原则:
- (1)
happens-before
(2) (程序顺序规则)- (2)
happens-before
(3) (volatile规则)- (3)
happens-before
(4) (程序顺序规则)
因此 (1)happens-before
(4),线程B一定能看到x=42
。
四、JMM的设计动机
屏蔽底层差异
- 不同CPU架构(x86、ARM)内存模型不一致(如内存屏障指令)。
- JMM提供统一抽象,使Java程序跨平台行为一致。
平衡性能与正确性
- 工作内存机制:线程通过本地副本操作数据,减少直接访问主内存的次数,极大提升性能(类比CPU缓存)。
- 按需同步:开发者通过
volatile
、synchronized
等关键字按需控制同步,避免无谓的性能损耗。
解决可见性与重排序问题
- 可见性问题:线程A修改变量后未同步到主内存,线程B读取到旧值。
- 重排序问题:编译器/处理器重排序导致代码执行顺序与预期不符(如单例模式的
DCL
问题)。 - JMM通过内存屏障(Memory Barrier)在底层禁止某些重排序,保障有序性。
提供明确的并发语义
- 通过
happens-before
原则,让开发者无需理解底层细节即可编写正确的并发代码。
- 通过
五、关键字的语义
关键字 | 原子性 | 可见性 | 有序性 |
---|---|---|---|
synchronized |
是 | 退出锁时同步到主内存 | 临界区内禁止重排序 |
volatile |
否 | 每次访问从主内存读写 | 禁止该变量相关指令重排序 |
final |
- | 构造函数内正确初始化 | 禁止final字段初始化重排序 |
final的特殊规则:
若对象引用为final
,则其他线程能看到该对象时,其final
字段一定已初始化完成。
六、为什么这样设计?
性能优化
工作内存机制减少主内存访问(类似CPU缓存),大幅提升执行效率。硬件与编译器友好
允许编译器和处理器在遵守happens-before
的前提下自由优化(如指令重排序、寄存器分配)。开发者友好性
通过抽象层(而非暴露硬件细节)让开发者更易编写正确的并发程序。可移植性
统一的内存模型使Java程序在不同硬件和操作系统上行为一致。
总结
Java内存模型通过主内存与工作内存的分离、happens-before规则及内存屏障技术,在保障多线程可见性、有序性与原子性的同时,兼顾了执行效率与跨平台一致性。其本质是在硬件差异与程序正确性之间建立了一层抽象契约,让开发者能更专注于业务逻辑,而非底层并发细节。理解JMM是编写高效、可靠并发程序的基础。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 技术之路!
评论