volatile关键词的使用和内幕

2018-01-24  本文已影响0人  AltairZ

一些知识总结,主要是从深入理解JVM虚拟中摘抄的。

语义

多线程的可见性和禁止指令重排序优化

JAVA中内存模型

所有的变量都是保存在主存中的,然后每一个线程都有自己的工作内存,使用每个变量之前需要把变量值从主存读到工作内存中。

主存和工作内存之间的交互协议,java内存模型中定义了以下几种操作来完成。

lock和unlock、read和load、use、assign、store和write

lock和unlock就是把主内存的变量标识为线程独占的状态,其他线程无法进行操作。并且同一个线程可以对变量进行多次lock然后有响应的unlock,这两个是组队出现的。

read和load

read是把主存中变量的值读入到工作内存中来。load是把read到工作内存的变量值放入变量副本中。组对出现

use是把变量值传递给执行引擎。

assign就是把执行引擎的值赋给工作内存中的变量。

store和write

store是把工作内存的值传送到主存中,以便后续的write使用。write就是把store到主存中的值放入主存的变量中。组对出现。

虽然lock和unlock等这些操作是组对出现的,但是这些操作并不一定是连续执行的,有可能中间会插入其他的操作。

java内存模型的规则

1、不允许read和load、strore和write单出出现,即不允许一个变量从主存读取了但是工作内存不接受,或者从工作内存发起回写了但是主存不接受的情况出现。

2、不允许一个线程丢弃它最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主存。

3、一个新的变量只能再、在主存中诞生,不允许在工作内存中直接使用一个未被初始化(load或者assign)的变量,就是对一个变量实施use、store之前必须先执行过了load、assign操作。

4、一个变量只允许同时被一个线程进行lock操作。

5、如果对一个变量执行lock操作,那就会清空工作内存中此变量的值,在执行引擎使用这个变量需重新执行load或者assign操作初始化变量的值。

6、如果一个线程事先没有被lock住,不允许对它执行unlock操作,也不允许unlock其他线程lock住的变量;

7、对一个变量执行unlock操作之前,必修把此变量的值回写回主存中。(store和write)

理解起来有困难可以看一下java的happen-before原则,此项内容以后补充。

volatile的特殊规则

1、只有当线程对变量执行的前一个动作是load的时候,线程才能对变量执行use操作;并且只有线程对变量执行的后一个动作事use时,线程才可以对变量执行load操作。(使用变量之前必须先从主存中刷新)

2、只有当线程对变量执行的前一个动作是assign时,线程才可以对变量执行store操作;并且只有线程对变量执行的后一个工作是strore时,线程才可以对变量执行assign操作。(修改变量值之后必须立刻回写主存)

3、如果线程1对变量A的操作是read、load、use,其中read的动作是A,load的动作是L,use的动作是X;线程2对变量B的操作是assign、store、write,其中assign操作是B,store动作是M,write是Y。如果A早于B,则X早于Y。(禁止指令重排序优化,保证代码的执行顺序和程序的顺序相同)

对第3条本人也不是很理解,按照我个人的理解其实应该是A早于B,则L和X都应该早于B的。我找了一些资料对这方面讲解的都比较少,基本都是围绕回写主存和CPU的缓存一致性讲解的,对规则性的东西比较少。我也尝试查看汇编源码,但是由于我对汇编实在是了解的太少,很多不明白的指令。不过我从LOCK指令推断,应该是我理解的样子。

计算机的角度(volatile实际的原理)

JVM在执行volatile关键词修饰的赋值操作时,会加入一个LOCK指令,此LOCK指令就形成了类似于内存屏障的功能。LOCK的作用就是把本CPU的缓存数据写入内存,并且这个动作也会让其他CPU引用了该内存地址的缓存行失效,使用就需要重新读取加载。通过这个操作,让前面对volatile变量的修改对其他CPU是可见的。

这个也会禁止指令重排序优化。原因是把volatile变量的修改同步到主存时,意味着所有之前的操作都已经执行完成了,这样便形成了“指令重排序无法越过内存屏障”的效果。

上一篇 下一篇

猜你喜欢

热点阅读