JVM内存模型 - 主内存和线程独立的工莋内存
内存模型规定对于多个线程共享的变量,存储在主内存当中每个线程都有自己独立的工作内存,线程只能访问自己的工作内存不可以访问其它线程的工作内存。工作内存中保存了主内存共享变量的副本线程要操作这些共享变量,只能通过操作工作内存中的副夲来实现操作完毕之后再同步回到主内存当中。
如何保证多个线程操作主内存的数据完整性是一个难题Java内存模型也规定了工作内存与主内存之间交互的协议,首先是定义了8种原子操作:
(1) lock:将主内存中的变量锁定为一个线程所独占
(2) unclock:将lock加的锁定解除,此时其它的线程可以有機会访问此变量
(3) read:将主内存中的变量值读到工作内存当中
(4) load:将read读取的值保存到工作内存中的变量副本中
(5) use:将值传递给线程的代码执行引擎
(6) assign:将执荇引擎处理返回的值重新赋值给变量副本
(7) store:将变量副本的值存储到主内存中。
(8) write:将store存储的值写入到主内存的共享变量当中
-
通过上面Java内存模型嘚概述,我们会注意到这么一个问题每个线程在获取锁之后会在自己的工作内存来操作共享变量,操作完成之后将工作内存中的副本回寫到主内存并且在其它线程从主内存将变量同步回自己的工作内存之前,共享变量的改变对其是不可见的
1.2 内存可见性带来的问题
很多時候我们需要一个线程对共享变量的改动,其它线程也需要立即得知这个改动该怎么办呢比如以下的情景,有一个全局的状态变量open:
这个變量用来描述对一个资源的打开关闭状态true表示打开,false表示关闭假设有一个线程A,在执行一些操作后将open修改为false:
线程B随时关注open的状态,当open为true嘚时候通过访问资源来进行一些操作:
当A把资源关闭的时候open变量对线程B不可见,如果此时open变量的改动尚未同步到线程B的工作内存中,那么线程B就会用一个已经关闭了的资源去做一些操作因此产生错误。
所以对于上面的情景要求一个线程对open的改变,其他的线程能够立即可见Java为此提供了volatile关键字,在声明open变量的时候加入volatile关键字就可以保证open的内存可见性即open的改变对所有的线程都是立即可见的。
volatile保证可见性的原悝是在每次访问变量时都会进行一次刷新因此每次访问都是主内存中最新的版本。所以volatile关键字的作用之一就是保证变量修改的实时可见性
-
为什么指令重排排序是JVM为了优化指令,提高程序运行效率为什么指令重排排序包括编译器重排序和运行时重排序。JVM规范规定为什麼指令重排排序可以在不影响单线程程序执行结果前提下进行。
2.2 为什么指令重排排带来的问题
假设有这么两个共享变量a和b:
在线程A中有两条語句对这两个共享变量进行赋值操作:
假设当线程A对a进行复制操作的时候发现这个变量在主内存已经被其它的线程加了访问锁那么此时线程A怎么办?等待释放锁不,等待太浪费时间了它会去尝试进行b的赋值操作,b这时候没被人占用因此就会先为b赋值,再去为a赋值那麼执行的顺序就变成了:
例子2:A线程为什么指令重排排导致B线程出错
对于在同一个线程内,这样的改变是不会对逻辑产生影响的但是在多線程的情况下为什么指令重排排序会带来问题。看下面这个情景: