Java内存模型
1.主内存与工作内存
Java内存模型的主要目标是定义程序各个变量的访问规则.即在虚拟机中将变量存储到内存,和从内存中取出变量.
Java内存模型规定了所有的变量都存储在主内存中.每条线程还有自己的工作内存.
线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝(只会拷贝使用到的).线程对变量的所有操作(读取,赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量.不同线程之间无法直接访问对方工作内存中的变量.线程间变量值的传递需要通过主内存来完成.
其中,主内存可近似理解为Java堆中的对象实例数据,工作内存可近似理解为虚拟机栈中的部分区域数据.
数据在线程间是隔离的,需要通过主内存作为媒介来进行数据交流.
2.内存间交互操作
Java中定义了8种操作来完成主内存与工作内存之间的具体交互,即一个变量是如何从主内存拷贝到工作内存,如何中工作内存同步会主内存.
虚拟机实现时必须保证这8种操作都是原子的,不可再分的.
-
lock(锁定):作用于主内存变量,把一个变量标识为一条线程独占的状态.
-
unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程加载.
-
read(读取):作用于主内存变量,把一个变量的值从主内存传输到线程的工作内存,以便load操作.
主内存->工作内存
-
load(载入):作用于工作内存的变量,把read操作从主内存得到的变量值放入工作内存的变量副本中.
->工作内存副本
-
use(使用):作用于工作内存的变量,把工作内存中变量的值传递给执行引擎.
工作内存->执行引擎
-
assign(赋值): 作用于工作内存变量,把一个从执行引擎接收到的值赋值给工作内存中的变量.
-
store(存储):作用于工作内存变量,把工作内存中一个变量的值传送到主内存中.
-
write(写入):作用于主内存变量,将store操作从工作内存中得到的变量放入主内存的变量.
执行上面的这8种操作时,需要遵循以下的规则:
-
不允许read和load,store和write单独出现. 读取则必须载入,存储则必须写入.
-
不允许一个线程发生了assign操作,但没有但没有同步到主内存.
即assign则必须同步回主内存
. -
不允许没有assign操作,就同步回主内存.
即赋值了就必须同步回主内存,没赋值就不能同步回主内存.
-
不允许在工作内存中直接使用一个未被初始化的变量,
即必须先assign或者load,才能use或store
-
一个变量同时只允许一条线程对其lock,但该条线程可以多次lock,.多次lock需要多次unlock.
-
对一个变量执行lock操作,会清空工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign.
-
不允许unlock一个未被lock的变量.
-
对一个变量执行unlock前,必须先把此变量同步回主内存中.
3.volatile关键字
volatile关键字是Java提供的最轻量级同步机制.当一个变量使用volatile修饰后,其具备两种特性:
第一: 保证此变量对所有线程的可见性.
第二: 禁止指令重排序优化.
可见性: 指当一条线程修改了这个变量的值,新值对于其他线程来说是立即可见的.
但volatile变量在各个线程的工作内存中是可能存在不一致的情况,但是由于每次使用volatile变量之前,都需要刷新此变量,所以对执行引擎来说是看不到不一致的情况的.(就是说每个线程使用的时候,肯定都是使用最新的变量值.这就是常说的缓存失效问题.
).
指令重排优化: 从硬件架构上讲,指令重排序是指CPU采用了允许将多条指令不按照程序规定的顺序分开发送给各相应电路单元处理.但并不是随意重排,CPU需要能正确处理指令依赖情况,以保证程序能得出正确的执行结果.意思就是你写的代码,只要能保证执行结果是正确的,CPU在执行的时候并不一定是按照你写的代码顺序那样去执行,只要前后没有依赖关系,写在后面的代码有可能先执行
.
为什么volatile可以禁止指令重排呢?
因为使用volatile修饰的变量,赋值后多执行了一个lock addl
操作,这个操作相当于一个内存屏障,指令重排时,不能把后面的指令重排序到内存屏障之前的位置. lock使得本CPU的Cache写入了内存,该动作也会引起别的CPU或内核的Cache失效.相当于对cache中的变量做了一次store和write操作.(即强制将cache写入主内存)
volatile变量的读操作性能与普通变量基本一致,写操作由于需要插入内存屏障,所以稍微慢一些.
Java内存模型对volatile的特殊规则
-
只有线程对变量的前一个操作时load,才能对变量执行use; 只有线程的后一个动作是use,前一个操作才能是load.
即对于volatile变量,load和use必须捆绑出现
-
只有当线程对变量的前一个操作时assign,才能执行store.同上,
对于volatile变量,assign和store必须捆;在工作内存中修改了volatile变量,必须立刻同步到主内存,保证其他变量及时收到
.
4.原子性,可见性和有序性
Java内存模型是围绕着在并发过程中如何处理原子性,可见性和有序性三个特征建立的.哪些操作实现了这三个特征呢?
原子性: read,load,assign,use,store,write等,保证了原子性.读取则必须载入,存储则必须写入等
.
可见性: 当一个线程修改了共享变量的值,其他线程能够激励得知这个修改.使用volatile实现.synchronize
和final
关键字也可以实现可见性. final关键字修饰的变量值不可变.
有序性: 如果在本线程内观察,所有操作都是有序的.如果在一个线程中观察另一个线程,所有操作都是无序的(指令重排优化).volatile禁止指令重排,synchronized实现一个变量在同一时刻只允许一条线程对其进行lock操作.