Java内存模型与线程
一、JMM定义
- JMM是Java 虚拟机规范中定义的一种标准,其目的是为了屏蔽各种硬件和操作系统的差异(不同硬件平台的差异),让Java程序在各种平台下都能达到一致的访问效果;
定义JMM并不容易,一方面需要足够严谨而不产生歧义,其次也必须足够宽松让虚拟机的实现有足够的自由空间去利用硬件的
特性(寄存器、Cache以及不同平台特有的指令)
二、JMM细节
2.1 主存与工作内存
- JMM规定线程对变量的操作都必须在线程的工作内存中进行,而不能直接读写主内存中的变量。不同线程之间也无法访问对方工作内存中的变量,线程之间的变量传递需要通过主内存来完成。
2.2 内存交互
- JMM 规定了8种操作来完成变量在主存和工作内存之间的交互,按照读取到工作内存然后使用最后写会主存的顺序分别是:lock、read、load、use、assign、store、write、unlock
操作 | 作用对象 | 作用 |
---|---|---|
lock | 主存变量 | 锁定主存中的变量,标识为某线程占有状态 |
unlock | 主存变量 | 解锁主存中的变量,解锁后其他线程才能占用 |
read | 主存变量 | 读取主存中变量到线程工作内存 |
write | 主存变量 | 将工作内存中变量值写入主存 |
load | 工作内存变量 | 将read读取到工作内存中的变量值放入工作内存中的变量副本 |
use | 工作内存变量 | 使用工作内存中的变量值 |
assign | 工作内存变量 | 工作内存中变量赋值 |
store | 工作内存变量 | 将工作内存中变量值传回主存 |
- 注意图中的步骤JMM做了一些规定,比如read和load不允许出现一个,换言之要load必须先read,read后也不能没有load,write和store也一样,不过需要注意的是,read和load直接是可以插入其他指令的,read a 和load a中间可以有其他指令。另外比如lock的次数和unlock的次数要对应才能正确的释放,执行unlock的时候会将工作内存变量同步到主存,等;
2.3 volatile
- 关于 volatile 变量,本文不深入开展,很多只是给出结论,其底层使用内存屏障指令来保证可见性和禁止指令重排序这两大特性,具体可以参考:16-Java多线程、volatile关键字
2.4 先行发生原则
-
先行发生原则是JMM 中定义的两项操作之间的时序关系,A 先行发送于B,意味着B发生时,A所产生的影响都能被B感知到,比如对变量的修改,方法的调用以及其他操作,JMM定义了一系列规则来约束该原则;
-
程序次序原则:一个线程内的控制流顺序中,前面的操作先行发生于后面的操作
-
管程锁定原则:unLock操作先行发生于后面对同一个锁的lock操作;(这里的先后是指时间上的)
-
volatile变量原则:对volatile变量的写操作现行发生于对这个变量的读操作
-
线程启动原则:线程Thread对象的start方法先行发生于线程的每一个动作
-
线程终止原则:线程中的操作先行发生于对这个线程的终止检测,比如Thread.join()和Thread.isAlive()判断线程是否已经终止;
-
传递性:A先行发生于B,B先行发生于C,那么A先行发生于C
Java与线程
-
Java中线程是模型是1:1,这里指的是每一个用户线程都对应一个内核线程,用户线程通过内核线程的高级接口实现,每个线程由内核操作调度器来实现调度,由此单个线程阻塞也不影响整个进程。但是这样带来了一些弊端:一方面线程的创建,销毁代价比较大,另一方面因为内核线程的数量受限,因此创建线程的数量也受到限制;
-
线程的状态切换:
这里补充一下各种可能导致阻塞的情况:
- Thread.sleep:释放Cpu,不释放锁
- object.wait:等待锁
- thread.join:底层是调用thread.wait,排队等待thread线程
- LoclSupport.park():挂起线程