Volatile概念
volatile是一个特征修饰符(type specifier)。volatile的做用是做为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。——百度百科java
因此呢它主要是两个做用:一个是线程可见(保证了不一样线程对这个变量进行操做时的可见性,即一个线程修改了某个变量的值,这新值对其余线程来讲是当即可见的。),一个是防止指令重排序。要理解这些首先呢须要了解咱们java的一个内存模型(Java Memory Model,JMM)git
Java Memory Model
咱们知道在java中,实例域、静态域和数组元素都存储在堆内存中,堆内存是线程共享,而其余的一些虚拟机栈等它们的的一些内容是线程独占不会有内存可见的问题也不受内存模型影响。Java线程之间的通讯由Java内存模型控制,JMM决定一个线程对共享变量的写入什么时候对另外一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每一个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。Java内存模型的抽象示意图以下:程序员
线程从主内存拿取到某以变量到本身本地内存进行操做,完毕以后再将新的值覆盖到主内存。以后再有其余线程拿到此变量获得一个新的值。经过这样的方式达到了一个不一样线程之间的通信,并且这个通讯过程必需要通过主内存。JMM经过控制主内存与每一个线程的本地内存之间的交互,来为java程序员提供内存可见性保证。数组
线程可见
当一个线程修改了共享变量的值,其余线程可以当即得知这个修改,这样的方式来保证单次读写操做的同步性。缓存
例子1:j的值会是多少呢?微信
// 线程A执行的代码
k = 5;
//线程B执行的代码
int j = k;
答案是没法肯定。由于即便线程A已经把k的值更新为5,可是这个操做是在线程A的本地内存中完成的,本地内存所更新的变量并不会当即同步回主内存,所以线程B从主内存中获得的变量k的值是不肯定的。这就是可见性问题,线程A对变量k修改了以后,线程B没有当即看到线程A修改的值。多线程
例子2: 新线程会打印出end么?ide
public class Test {
private static /*volatile*/ boolean flag = true
public static void main (String[] args) throws I interrupted Exception {
new Thread(()-> {
while (flag) {
//do sth
}
System•out•println("end");
},name: "server") .start();
Thread.sleep( millis: 1GGG);
flag = false
}
}
答案是不会,新线程的本地内存拿到的flag是true,它一直使用的就是true。即便主线程已经将flag更改并同步到了主内存。新线程的本地空间已经有了flag也不会再去主内存取了。这时使用volatitle关键字修饰该变量就能够保证变量更改进行马上同步,而且其余地方使用该变量每次都要从新从主内存拿取。性能
经过两个例子大概能够知道的是volatile修饰的变量,变更会及时更新而且线程都会去主内存取而不是到本地优化
指令重排序
实际上就是在执行程序时为了提升性能,编译器和处理器经常会对指令作重排序。
编译器优化的重排序。编译器在不改变单线程程序语义的前提下,能够从新安排语句的执行顺序。
指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。若是不存在数据依赖性,处理器能够改变语句对应机器指令的执行顺序。
内存系统的重排序。因为处理器使用缓存和读/写缓冲区,这使得加载和存储操做看上去多是在乱序执行。
这里先用一个列子说明重排序的存在,看下面伪代码:
a=0,b=0,x=0,y=0
//线程一
a=1;
x=b;
//线程二
b=1;
y=a;
假如不会发生重排序。那么执行过程当中两个线程四条指令至少a=1必定在x=b以前,b=1必定在y=a以前。执行顺序可能出现六种状况
//状况一:(串)线程一执行完后线程二才开始
a=1;
x=b;
b=1;
y=a;
//结果x=0,y=1
//状况二:(串)线程一开始时线程二已执行完
b=1;
y=a;
a=1;
x=b;
//结果x=1,y=0
//状况三:(并)两线程交叉执行
a=1;
b=1;
x=b;
y=a;
//结果x=1,y=1
//状况四:(并)两线程交叉执行
b=1;
a=1;
y=a;
x=b;
//结果x=1,y=1
//状况五:(并)线程一执行中途线程二开始并执行完
a=1;
b=1;
y=a;
x=b;
//结果x=1,y=1
//状况五:(并)线程二执行中途线程一开始并执行完
b=1;
a=1;
x=b;
y=a;
//结果x=1,y=1
在不会被调整顺序的状况中结果无非三种(1,0)、(0,1)、(1,1)但实际结果会出现x=0,y=0。也就是说线程的指令是乱序的会进行调整。对于单线程来讲调整是不会影响结果的只是提高了效率好比省略一加一减相互抵消的指令或者调整顺序,最后结果不影响。
/*
下面这三组就不会发生指令重排
由于改了顺序就会影响结果
*/
a=1;
b=a;
a=1;
a=2;
a=b;
b=1;
在上面双线程的例子中不管是线程一的a=1,x=b仍是线程二的b=1,y=a。在它们本线程中两条语句并非依赖的,因此调换不影响结果因此会出现调换。但两个线程放一块儿变量是依赖的,最后由于重排致使结果不一致。因此在多线程中每每会出现问题因此须要禁止重排,使用volatile那么指令之间加入内存屏障指令就能够禁止重排。
本文分享自微信公众号 - IT那个小笔记(qq1839646816)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。