JMM
Java 内存模型(Java Memory Model)屏蔽了不同操作系统和各种硬件的内存访问差异,实现了 Java 程序在各种平台下,都能达到一致性的访问效果。
JMM 规定了:
- 所有的变量都存储在主存中
- 每条线程有自己的工作内存
- 线程对变量的所有操作(读取、赋值等),不能直接读写主存中的数据,都必须在工作内存中进行
原子性、可见性、有序性
Java 内存模型是围绕着在并发过程中如何处理原子性、可见性和有序性三个特征来建立的。
🐥 原子性
一次或多次操作,要么全部执行且不受到任何干扰,要么全都不执行。
- 利用锁,可以让任一时刻只有一个线程访问代码块。
synchronized
Lock
- 利用
CAS
原子类,保证原子操作。
🐤 可见性
一个线程修改了共享变量的值,其他线程能够立即得知这个修改。
volatile
规定每次写操作,都立即同步到主存;每次读操作,都从主存中读取。
synchronized
lock
对一个变量执行执行 unlock 之前,必须把变量同步回主存中。
final
🐣 有序性
指令重排问题,代码的执行顺序不一定。
volatile
包含禁止指令重排序的语义。
synchronized
只有一条线程能够进入临界区。
volatile
Java 虚拟机提供的轻量级内存同步机制。
作用:
- ✔️ 保障了此变量对所有线程的可见性
volatile 的写操作,都会刷新到主内存中,并使其他线程中的 volatile 变量失效;volatile 的读操作,都会从主存中读取。
- ✔️ 禁止指令重排优化(有序性)
防止写操作之前的代码,重排到写操作之后;防止读操作之后的代码,重排到读操作之前。
- ❗️无法保障原子性
一条字节码在执行时,是需要运行多条指令才能实现。
实现方式:内存屏障。重排序时,不能把后面的指令重排序到内存屏障之前的位置。
保证:之前的指令一定全部执行,之后的指令一定都没有执行,并且前面语句的结果对后面的语句可见。
happens-before 原则
对于两个操作 A 和 B,这两个操作可以在不同的线程中执行。如果 A happens-before B(A 优先于 B 执行),那么可以保证,当 A 操作完后,A 的操作对于 B 操作是可见的。
指令重排提高了并发性能,但是 Java 虚拟机会对指令重排做一些规则限制,并不能让所有的指令都随意改变执行位置。
Java 内存模型天然的先行发生关系:
程序顺序规则
在一个线程内,前面的代码操作优于后面的代码操作。
锁定规则
一个 unlock 操作优于后面对于同一个锁的 lock 操作。
volatile 规则
一个 volatile 变量的写操作,优于后面这个变量的读操作。
传递规则
如果 A happens-before B,且 B happens-before C,那么 A happens-before C。
线程启动规则
Thread 对象的 start() 方法 happens-before 线程的每一个动作。