参考书籍:java并发编程详解、深入理解java虚拟机
为什么聊volatile那?
自JDK1.5版本起,volatile关键字锁扮演的角色越来越重要,该关键字也成为并法包的基础,所有的原子数据类型都以此作为修饰,相比synchronized关键字,volatile被称为“轻量级锁”,能实现部分synchronized关键字的语义。
要说volatile首先要介绍普通变量(不被volatile修饰),可有对比性,再次之前还要先了解一些处理器、高速缓存、主内存之间的关系
看图
两图对比这来
这里讲的主内存,工作内存与堆区和栈方法区等不是同一个层次的内存划分,这两者之间基本上没有关系,如果两者要勉强对应起来,那么那从变量,主内存,工作内存的定义来看,主内存主要对应的是堆区对象实例数据部分,而工作内存对应虚拟机栈中的部分区域,从更低层次上说,主内存就是直接对应物理硬件的内存,而为了更好的运行速度,虚拟机可能会让工作内存有限存储于寄存器和高速缓存中,因为程序运行时主要访问读写的是主内存
每个线程都有自己的私有内存空间也就是所谓的工作内存
分别介绍一下这六个操作
read(读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作的使用。
load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量放入工作内存的变量副本中。
use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用的变量的值的字节码指令时将会执行这个操作。
asign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传递到主内存中,以便随后的write操作使用。
write(写入):作用于主内存的变量,它把stroe操作从工作内存中得到的变量的值放入主内存的变量中。
如果要把一个变量从主内存复制到工作内存,那就要顺序地执行read和load操作,如果要把变量从工作内存同步回到主内存,就要顺序地执行stroe和write操作,注意内存模型只要他们是顺序执行并没有要求他们连续执行。
多线程并发状况下普通变量在这六步的操作下会有怎样的效果那?
假如主内存中有一个变量a=1,线程1 进行了操作read load把主内存变量值读取并载入线程1的工作内存后(工作内存a=1),cpu切换去执行线程2,线程2加入也执行了read和load操作(工作内存a=1)并且顺利进行了对a进行加1操作然后顺利执行assign,线程2的工作内存a=2,然后线程2将a=2写回主内存,对线程1无任何影响,所以线程1的a仍是1执行加1后仍是2,写回主存,最后主存a=2
那如果换成volatile会怎么样那?
首先说一下volatile的操作:read,load,use规定是连续发生的;assign,stroe,write是连续发生的。
假如主内存中有一个变量a=1,线程1 进行了操作read load use把主内存变量值读取并载入线程1的工作内存后(工作内存a=1)并使用,正要进行加1操作时,cpu切换去执行线程2,此时会把线程1的a=1缓存到寄存器中,线程2加入也执行了read和load use操作并且顺利进行了对a进行加1操作然后又顺利执行assign和 stroe write,此时主内存的a=2,由于主内存的a=2根据一致性线程1的工作内存进行刷新a=2,但是不影响寄存器中的a=1,(因为切换到线程2的时候线程1中存在寄存器的a=1,所有操作之后a=2),然后线程1再把a存储并写回后,主内存中a的值还是2,也就是说两个线程都对a进行加1但是最后的结果是2.
为什么都会发生这种问题那volatile还有什么用那?volatile的作用在哪?
举个例子啊,如果线程1
public void run(){
while(a==2){
break;
}
}
线程2
public void run(){
a=2;
}
如果是普通变量a的话 ,线程2进行赋值后a=2,回写到主存后,主存a=2,此时线程1工作内存仍是a=1,所以线程1将进入死循环。
如果是volatile变量那由于assign stroe write是连续操作这编赋值后直接连续操作这三个步骤把a=2放入主内存中,接着线程1进行使用a的时候会重新进行操作read,load,use,所以获取的是主存a=2,然后结束循环(因为每次进行use之前都要重新进行read,load)
这个例子也就是volatile的可见性
但是volatile和普通变量最大的区别就在于此
特殊情况 a++ 首先a++不是一个原子操作,他会进行先读a,然后对a进行加1操作,接着对a进行写回
所以你也可以说volatile的读写操作是原子性的,这里的读(write,load,use);写(assign,stroe,write)
read,load保持原子性,但是read,load,use必须是连续出现的,所以保证了每次使用a的时候都是从主内存中获取到的新的,
assign,stroe保持原子性,保证了每次赋值后立即返回到内存中
但是主内存在进行刷新之后
有序是什么?
因为java代码是有序的但是在底层进行指令集操作处理的时候由于指令集的优化问题将会导致指令集的重排序,所以会出现一些问题,比如 int i=0; int j=1;看似是i=0先执行在 j=1之前但是底层的指令集有可能会导致先执行j=1然后再执行i=0; 所以会出现一个问题 ,如果你把int j=1 换成 volatile int j=1将不会出现以上问题,因为volatile将会在int j=1之前添加一个内存屏障,来保持volatile修饰的变量与之前的代码保持java代码形式上的有序
以上都是个人理解有什么不同的看法可以留言,大家一起学习