深入浅出Java内存模型(JMM)
一、JMM是啥?
Java内存模型(JMM)就像多线程世界的交通规则:
- 作用:定义线程如何与内存交互,防止"数据车祸"
- 核心问题 :解决多线程的可见性 、有序性 、原子性问题
- 类比 :
- 内存:共享的快递仓库
- 线程:搬运工人
- JMM规则:仓库的存取管理制度
二、三大核心问题
1. 可见性问题
java
// 线程A
flag = true; // 写操作
// 线程B
while(!flag); // 可能永远死循环!
问题 :线程A改了flag,线程B看不见
解决 :用volatile
或synchronized
2. 有序性问题
java
// 代码顺序
int a = 1;
int b = 2;
// 实际执行可能变成:
int b = 2;
int a = 1;
问题 :编译器/CPU会优化指令顺序(重排序)
解决 :用volatile
或final
3. 原子性问题
java
count++; // 实际包含3步操作:读→改→写
问题 :多线程同时操作会导致数据错误
解决 :用synchronized
或AtomicInteger
三、JMM关键概念
1. 主内存 vs 工作内存
graph LR
主内存 -->|读取| 线程A工作内存
主内存 -->|读取| 线程B工作内存
线程A工作内存 -->|写入| 主内存
线程B工作内存 -->|写入| 主内存
- 主内存:所有线程共享(相当于仓库)
- 工作内存:每个线程私有(相当于工人的手推车)
2. happens-before原则(重点!)
JMM规定的操作先后保证规则:
- 程序顺序规则:同一线程内,代码顺序优先
- 锁规则:解锁操作先于后续加锁操作
- volatile规则:写操作先于后续读操作
- 传递性:A先于B,B先于C → A先于C
示例:
java
// 线程A
x = 1; // 操作1
volatileFlag = true; // 操作2(volatile写)
// 线程B
if(volatileFlag) { // 操作3(volatile读)
print(x); // 保证看到x=1!
}
四、volatile关键字
1. 两大特性
- 可见性:写操作立即刷新到主内存
- 禁止重排序:优化屏障
2. 底层实现
java
// Java代码
volatile boolean flag = false;
// 对应汇编指令(带lock前缀)
0x01a3de24: lock addl $0x0,(%esp);
3. 使用场景
- 状态标志位(如
volatile boolean running
) - 单例模式双重检查锁定
java
class Singleton {
private static volatile Singleton instance;
static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
五、synchronized机制
1. 内存语义
- 进入同步块:清空工作内存,从主内存重新加载
- 退出同步块:将工作内存写回主内存
2. 锁升级过程
graph LR
无锁 --> 偏向锁 --> 轻量级锁 --> 重量级锁
- 偏向锁:只有一个线程访问时生效
- 轻量级锁:少量线程竞争时用CAS自旋
- 重量级锁:真正调用操作系统互斥量
六、final的内存语义
1. 特殊规则
- 构造函数内:正确初始化的final字段,对其他线程立即可见
- 禁止重排序:防止构造函数未完成就暴露对象引用
2. 安全发布示例
java
class FinalExample {
final int x;
static FinalExample instance;
public FinalExample() {
x = 42; // final写
}
static void writer() {
instance = new FinalExample(); // 安全发布
}
static void reader() {
if (instance != null) {
System.out.println(instance.x); // 保证看到x=42
}
}
}
七、JMM与硬件关系
Java概念 | 硬件对应 |
---|---|
主内存 | RAM内存 |
工作内存 | CPU缓存/寄存器 |
内存屏障 | CPU内存屏障指令 |
volatile | lock指令+CACHE一致性协议 |
八、常见误区
1. volatile ≠ 原子性
java
volatile int count = 0;
count++; // 仍然不是原子操作!
2. synchronized性能差?
- 现代JVM已大幅优化(偏向锁/自适应自旋)
- 竞争不激烈时开销很小
3. final只在编译期有效?
- 运行期仍有内存语义保证
九、终极口诀
"JMM管线程内存,三大问题要记牢
可见有序加原子,volatile能解前两样
锁住同步最保险,happens-before是准绳
内存屏障底层撑,多线程安全有保障"
理解JMM,你就能写出线程安全的高性能代码! 🔒➡️🚀