并发是程序界的量子物理,然而volatile又是量子物理中薛定谔的猫。本篇文章试图系统的梳理一下java中的Volatile关键字。这篇译文可能帮助你更好的理解volatile关键字。
使用volatile关键字是解决同步问题的一种有效手段。 java volatile关键字预示着这个变量始终是“存储进入了主存”。更精确的表述就是每一次读一个volatile变量,都会从主存读取,而不是CPU的缓存。一样的道理,每次写一个volatile变量,都是写回主存,而不只仅是CPU的缓存。
javascript
Java 保证volatile关键字保证变量的改变对各个线程是可见的。这看起来有点抽象,不过将紧接着说明这一点。
咱们知道,每个线程都有本身的线程栈。多线程在操做非volatile变量的时候,都会从主存拷贝变量值到本身的栈内存中间,而后再操做变量。在多个线程的状况下,若是一个线程修改了变量值还未回写到主内存,另外一个线程读取的就是一个旧的值,这样会出现问题,由于读到的变量不是最新的。实际上,在多核CPU中间,因为每一个CPU都有本身的缓存,一样会存在主存与CPU缓存之间数据不一致的状况。所以,在C语言中,也有volatile关键字。(译者注:实际上,若是在CPU的层面知足volatile特性,那么线程栈就必定知足。由于从volatile语义来说,jvm线程每次只从主存读写volatile变量,而主存的volatile变量又在CPU层面知足volatile语义)
想象一种这样的状况,有两个或者更多的线程访问一个共享对象,这个共享对象包括了一个counter变量:
public class SharedObject {java
public int counter = 0;复制代码
}
再想象一下,只有线程1对counter变量加一,可是线程一和线程2倒是同时读到这个变量。缓存
若是这个contouer变量没有被声明为volatile。
就不能保证counter变量从cpu缓存回写到主存。这就意味着counter变量在cpu缓存中的值与主存中值不一致。多线程
这就是所谓的线程不能看到变量最新值的问题。由于另一个线程并无及时将变量写回到主存。这样一个线程的人更新对其余线程是不可见的。并发
经过声明counter变量是一个volatile变量,这样全部counter变量的更改就会被当即写入主存。一样,对counter变量的读也从主存里面读。下面是如何声明一个volatile变量:app
public class SharedObject {
public volatile int counter = 0;
}复制代码
经过声明volatile变量就保证了对其余线程写的可见性。jvm
java5中的volatile关键字不仅是保证从主存中读写数据,实际上,volatile还保证以下的状况:性能
读和写volatile变量的指令没法被JVM重排序(JVM为了提升性能能够重排序一些指令,只要程序的行为与排序前同样)可是volatile变量却没法重排序,也就是volatile变量的读和写没法被打乱在其余变量中间。不论是什么指令,老是在volatile变量读写以后发生。优化
下面将会详细的解释这一点。ui
当一个线程写一个volatile变量,而后不只仅是volatile变量自己自身写入到主存。全部其余的在写volatile变量以前也会被刷入主存。当一个线程读volatile变量的时候,它也会从主存读取其余变量。(译者注:注意是全部的变量。每次在写入volatile变量的时候,线程栈里面的全部的共享变量都将刷回主存,而不只仅是在volatile变量声明以前的变量)
看下面这个例子,sharedObject.counter是一个volatile变量:
Thread A:
sharedObject.nonVolatile = 123;
sharedObject.counter = sharedObject.counter + 1;
Thread B:
int counter = sharedObject.counter;
int nonVolatile = sharedObject.nonVolatile;复制代码
当线程A写在写入volatile变量sharedObject.counter以前写入一个非volatile变量,而后再写入volatile变量,这个时候非volatile变量sharedObject.nonVolatile 也会被写入主存。
当线程B开始读一个volatile变量sharedObject.counter,而后全部的sharedObject.nonVolatile以及
sharedObject.counter都会从主存读取。这个时候sharedObject.nonVolatile值与线程A中的值是同样的。
开发者可使用这种扩展的可视性来优化线程之间的可视性:不是对每一个变量都声明为volatile变量,而是只须要声明其中一部分变量为volatile。下面是Exchanger类,就利用了上述的原则:
public class Exchanger {
private Object object = null;
private volatile hasNewObject = false;
public void put(Object newObject) {
while(hasNewObject) {
//wait - do not overwrite existing new object
}
object = newObject;
hasNewObject = true; //volatile write
}
public Object take(){
while(!hasNewObject){ //volatile read
//wait - don't take old object (or null)
}
Object obj = object;
hasNewObject = false; //volatile write
return obj;
}
}复制代码
线程A一遍又一遍的调用put()方法。线程B一遍又一遍的调用take方法。这个Exchanger可以在合理使用volatile关键字的状况下工做的很好。只要线程A只调用put方法,线程b只调用take方法。
然而,JVM是能够对指令进行优化的。若是JVM对指令优化,打乱了顺序,会出现什么样的效果呢?下面这段代码多是执行的顺序之一:
while(hasNewObject) {
//wait - do not overwrite existing new object
}
hasNewObject = true; //volatile write
object = newObject;复制代码
注意到volatile变量hasNewObject如今在object被设置以前执行了。这个对于JVM来讲看起来好像是合法的,由于这两个值的写入指令相互是没有依赖的,JVM能够对它们重排序。
然而,重排指令有可能影响到object变量的可见性。首先,线程B看见在线程A尚未对object赋值以前就看见了hasNewObject是一个true变量,这样操做线程B读取了一个空值。其次,这甚至不能保证object变量会被及时的写入到主存。(固然,下一次线程A更改volatile变量的时候就会被刷进主存)
为了阻止上面的任何一种状况发生,volatile保证了“happens before ”特性。happens-before特性保证volatile变量的读写不能被重排序。也就是对volatile变量的读写不能插入到其余的任何指令中。
看下面这个例子:
sharedObject.nonVolatile1 = 123;
sharedObject.nonVolatile2 = 456;
sharedObject.nonVolatile3 = 789;
sharedObject.volatile = true; //一个 volatile 变量
int someValue1 = sharedObject.nonVolatile4;
int someValue2 = sharedObject.nonVolatile5;
int someValue3 = sharedObject.nonVolatile6;复制代码
JVM 可能重排序前三个指令。只要他们所有在volatile写入指令前发生(他们必须在volatile写入前所有执行)
相似的,JVM可能重排序最后三个指令。只要volatile变量写操做在它们前发生。最后这三个指令都不能被排在volatile变量写指令前面。
这就是最基本的javavolatile变量的happens before原则。
即便是volatile关键字保证了读写都是从主存读取,然而仍然有写状况不能简单的使用variable变量来解决。在早先讲到的例子中,当线程1写入一个变量counter这个volatile以后,就能保证线程2读到这个最新的值。
事实上,若是线程在写volatile变量并不依赖于这个volatile以前的值,那么在写的过程当中,主存中仍然是当前的值。
而后一个线程开始读这个volatile变量。那么这个线程读到的值就是旧的值,可见性就是不正确的。这就会形成读变量和写变量之间的竞争。volatile关键字只是保证了下一次读取的是最新的变量,可是在另一个变量写入的过程当中,读到的值仍然是旧的。(译者注:若是是多个CPU先写后读,在写的过程当中实际上会发出信号,告知其缓存已经失效,因此并不会存在这种状况;至于先读后写,读取一个旧的值的时候要在代码里保证并不会引起任何错误。)。