深入理解Java虚拟机-JAVA内存模型与线程
Java内存模型(JMM)
JMM 的核心概念
主内存与工作内存:
- 主内存(Main Memory)是所有线程共享的内存区域,存放着所有变量的值
- 每个线程都有自己的 工作内存(Working Memory),它是该线程的私有内存区域。线程操作共享变量时,先从主内存将变量拷贝到工作内存中,然后对工作内存中的变量进行修改,最后再将修改结果写回主内存
共享变量:
- JMM共享变量是多个线程可以访问的变量。通常是
static
变量或者实例变量。局部变量是线程私有的,不受 JMM 的影响
内存屏障(Memory Barriers) :
- 内存屏障是指 CPU 或者编译器用来保证操作顺序的一种机制。它通过禁止指令重排,确保某些操作在执行时的顺序
变量的可见性、原子性与有序性:
- 可见性:当一个线程修改了共享变量的值,其他线程能够看到这个修改
- 原子性:对共享变量的操作要么完全成功,要么完全失败,不会中断。对于一些基本的操作(如
i++
)来说,JMM 并不保证其原子性,需要通过同步手段来确保 - 有序性:JMM 保证每个线程内的代码执行顺序,但不一定保证所有线程之间的执行顺序。为了确保线程之间操作的顺序,JMM 提供了同步机制来控制
JMM 中的关键规则
线程间的可见性保证:
- 可见性问题的核心是,当一个线程修改了共享变量,其他线程如何及时看到这个修改。JMM 的设计通过内存同步(比如锁机制、
volatile
关键字、synchronized
关键字等)来确保可见性 volatile
关键字:声明为volatile
的变量会直接从主内存中读取,而不是从线程的工作内存中读取。写入volatile
变量时,JMM 会保证该写操作对其他线程可见。volatile
确保了可见性,但不能保证原子性和有序性
原子性保障:
- 在 JMM 中,只有一些基本的操作(如读取和写入一个
long
或double
类型的变量)是原子的。对于复合操作(如i++
),如果不加同步,可能会出现原子性问题 - 原子性问题的解决方法:使用
synchronized
、ReentrantLock
等同步机制来确保原子性
有序性保障:
- JMM 规定了每个线程内的指令执行顺序,但在不同线程之间,JMM 不保证执行顺序。为了控制执行顺序,可以使用
synchronized
、volatile
或Lock
等手段 synchronized
关键字:synchronized
用于确保代码块的互斥执行,并在释放锁时会刷新工作内存中的值到主内存,从而保证线程间的可见性和顺序性volatile
关键字:保证了变量的写操作立即刷新到主内存,且对该变量的读操作总是直接从主内存读取,避免了线程之间的数据不一致性
JMM 的实现和底层原理
- MESI 协议(Modified, Exclusive, Shared, Invalid),用于多核 CPU 之间缓存数据的一致性
- 内存屏障(Memory Barrier) :用于禁止指令重排,确保特定操作的顺序执行
volatile语义
可见性(Visibility)
- 保证:当一个线程修改了
volatile
变量的值,新值会立即被刷新到主内存;其他线程在读取该变量时,会从主内存中重新加载最新值 - 实现机制:通过插入
Memory Barrier
(内存屏障)或缓存一致性协议(如 MESI 协议)强制同步主内存和工作内存的数据
原子性(Atomicity)
- 单变量操作:对
volatile
变量的读写操作是原子性的(例如count++
不会被拆分为read+increment+write
) - 复合操作:
volatile
不能保证复合操作的原子性(例如i++
或a = b + c
),仍需借助synchronized
或AtomicInteger
等类
禁止指令重排序(Ordering)
- 编译器优化:编译器和处理器可能会对指令进行重排序以提高性能
- 读操作:在读取
volatile
变量前插入Load Barrier
,禁止之前的读/写操作被重排到其后 - 写操作:在写入
volatile
变量后插入Store Barrier
,禁止之后的读/写操作被重排到其前 - 效果:保证
volatile
变量的读写顺序符合程序逻辑
happens-before规则
程序顺序规则
- 同一线程内,代码执行顺序与书写顺序一致(编译器和处理器可能重排指令,但需保证单线程结果不变)
监视器锁规则
- Lock → Unlock:对同一锁的
synchronized
块,Lock
操作必在Unlock
前发生 - Unlock → Lock:
Unlock
后,其他线程的Lock
操作才能获取该锁
volatile 变量规则
- 写 → 读:对
volatile
变量的写操作,必在后续读操作之前完成 - 读 → 写:对
volatile
变量的读操作,必在后续写操作之前完成
线程启动规则
Thread.start()
必须在新建线程的任何操作之前发生
线程终止规则
- 线程的
run()
方法结束(正常或异常退出)必在join()
返回之前发生
中断规则
- 对线程的
interrupt()
调用必在该线程检测到中断状态(如isInterrupted()
)之前发生
对象终结规则
- 对象的
finalize()
方法执行完必在字段被垃圾回收之前发生(注:finalize()
已废弃)
传递性规则
- 若 A → B 且 B → C,则 A → C(可通过多条规则推导复杂顺序约束)