Java内存模型(Java Memory Model)
- 并不是真实物理上存在的
- 定义了:各种变量的访问规则
- 可以理解为:在特定的协议下,对特定的内存或高速缓存进行读写访问的「过程抽象」
- 目的是:屏蔽各种硬件和OS的内存访问差异,使所有 Java 程序再各平台下都能达到「一致性的内存访问」
理解变量
这里说的变量的访问规则中的变量,指的是「实例字段」,「静态字段」,「数组对象」
不包括「方法参数」和「局部变量」(线程私有,不需要共享)
硬件的效率与一致性
我们知道,指令重排发生在很多级别,比如CPU指令重拍(乱序执行,Out-Of-Order Execution),即时编译器指令重排序(Instrction Reorder)等等
注意,.class文件编译生成的字节码文件是不会指令重排序的
Java内存模型
内存模型
在特定的协议下,对内存或者缓存读写访问过程的抽象
Java内存模型
工作内存中保存了主内存中变量在线程中的本地副本
线程不直接访问共享内存,所有对变量的读写操作需要经过工作内存
目的
屏蔽不同硬件和OS的差异,提供统一的内存模型,使Java程序在各个平台下都能达到一致的内存访问效果
手段
定义程序中各种变量的访问规则
理解
这里的JMM和JVM运行时数据区是没有关系的
从硬件和OS的角度来说,主内存对应了Memory,而工作内存对应了Cache和Register
工作内存和主内存的交互
操作
有8个原子的不可再分的操作,分别是
-
lock把一个变量标记为线程独占的状态 -
unlock解除一个变量的线程独占状态 -
read主内存传输到线程的工作内存中 -
load接受主内存传输过来的变量并放到变量副本中 -
use工作内存中的变量副本传递给执行引擎 -
assing把从执行引擎接受的值赋给工作内存的变量副本 -
write工作内存的变量副本传输到主内存中 -
store主内存接受传输而来的值并放到主内存的变量中
规则
-
read/load、store/write必需同时发生 -
不能丢弃
assign(不能assign了但是不store) -
不能无理由的
store(没发生assign但是store) -
新变量只能在主内存中诞生(不能没有
assign/load就去use/store) -
一个变量同一时刻只能被同一线程
lock,可以lock多次 -
只有执行相同数量的
unlock才能解锁 -
unlock前必须同步回共享内存 -
不能
unlock被其他线程锁定的变量
long和double
我们之前说过,针对变量的操作必须是原子的,但是针对long和double这种64位长的变量来说,允许使用两条指令来完成
happens-before
Java内存模型的等价判断就是 happens-before 规则,
volatile关键字
volatile关键字是最轻量级的同步机制,它的两个作用是:禁止指令重拍和保证变量在不同线程之间的可见性,我们应该注意到,它不能保证对变量操作的原子性
每次 `use` 前都要 `read` 和 `load`
`assign` 后要 `store` 和 `write`
禁止指令重排序
-
禁止指令重拍
表现为
Within-Thread As-If-Serial Semantics在字节码文件中加入内存屏障,使得该屏障后面的不会被重排序到前面
-
可见性
这是通过对上述指令中操作顺序和连续性的约束来实现的,只能保证可见性,不能保证原子性
原子性、可见性、有序性
原子性
基本数据类型的访问都是具有原子性的
synchronized 能保证原子性
可见性
另一个线程能立刻看到当前线程对变量的修改结果
volatile、synchronized、final 都能保证可见性
有序性
表现为一个线程内是有序的,线程间是无序的
volatile(禁止指令重拍)、synchronized(同步)能保证有序性
Happens-Before
Happens-Before 先行发生原则用于判断线程是否安全
- 程序次序规则
程序执行流中,书写在前面的操作先行发生于书写在后面的操作
- 管程锁定规则
一个unlock操作先行发生于后面的lock操作
- volatile变量规则
对volatile变量的写先行发生于后面对volatile变量的读
- 线程启动规则
Thread对象的start()方法先行发生于此线程的每一个动作
- 线程终止规则
线程中所有操作先行发生于对此线程的终止操作
- 线程中断规则
对线程的interrupt()方法的调用先行发生于被中断线程的代码检测到中断发生
- 对象终结规则
一个对象的初始化完成先行发生于它的finalize()方法的开始
- 传递性
A -> B, B -> C => A -> C
时间先后顺序与先行发生原则之间没有因果关系
线程
线程的实现
-
使用内核线程实现
-
1 :1
-
一个用户线程对应一个内核进程
-
线程的切换开销较大
-
-
使用用户线程实现
-
1 :N
-
多个用户线程对应一个用户进程
-
内核感受不到用户线程的存在
-
切换在用户态完成,切换开销小
-
应用程序自己实现线程切换
-
-
用户进程加轻量级进程混合实现
- M:N
Java线程
HotSpot虚拟机将一个虚拟机线程映射到一个操作系统线程,调度交给操作系统完成(虚拟机栈和本地方法栈也是做到一起的)
《Java虚拟机规范》不规定Java线程的实现方式