这个Java的volatile关键字是用来标示一个Java变量作为“正在被存储在主内存的”。更加准确地说意味着,一个volatile变量的每一次读取都是从计算机的主内存中读取,而不是从CPU缓存中,并且对于一个volatile变量的每一次写将会写到主内存中,而不只是写入到CPU缓存中。
事实上,自从Java5开始这个volatile关键字不只是保证变量写到主内存,而且还从主内存中读取。我将会在接下来的部分中解释。
Java的volatile关键字的可见性的保证
这个Java的volatile关键字保证横跨线程中对于变量改变的可见性。这个可能听起来有点抽象,让我们详细阐述一下。
在一个多线程的应用中,线程操作在非volatile变量上,每一个线程在工作的时候可能会从主内存拷贝变量进入到CPU缓存中,因为性能原因。如果你的计算机包含不只是一个CPU,那每一个线程可能会运行在不同的CPU中。那就意味着,每一个线程就会拷贝变量进入到不同的CPU的CPU缓存中去。如下图所示:
使用非volatile的变量,这里不能保证JVM什么时间从主内存读取数据进入到CPU缓存中,或者写数据从CPU缓存进入主内存中。这个可能就会引起在下面部分将会解释的几个问题。
想象一个场景,两个或者更多的线程访问一个包含声明了一个counter变量的一个共享对象,像下面这样:
public class SharedObject {
public int counter = 0;
}
也想象一下,只有线程1增加counter变量,但是线程1和线程2偶尔会读取这个counter变量。
如果这个counter变量没有声明为volatile,那就不能保证这个counter变量什么时间从CPU缓存中写回到主内存中。这个就意味着,这个在CPU缓存中的counter变量跟在主内存中的值是不同的。如下图所示:
线程不能看到这个变量的最新的值的这个问题是因为它还没有被其他的线程写回到主内存中,别称之为”可见性”问题。一个线程的更新对于其他的线程是不可见的。
通过声明这个counter变量为volatile,对于counter这个变量的所有写入将会立刻写回到主内存中。同时,对于counter变量的所有读取将会直接从主内存中读取。这里有一个counter变量怎么样声明为volatile:
public class SharedObject {
public volatile int counter = 0;
}声明一个变量为volatile,因此可以保证针对这个变量的写对于其他线程的可见性。
这个Java的volatile关键字保证了前后顺序
自从Java5以来,这个volatile关键字不只是保证了变量从主内存的读和写。实际上,volatile关键字还保证了这个:
- 如果线程A写向一个volatile变量,以及线程B随后读取这个变量,然后在写这个volatile变量之前,所有的变量对于线程A是可见的,在它已经读取这个volatile变量之后也会对线程B可见的。
- volatile变量的读和写的指令不会被JVM重排序(只要JVM检测到只要来自于重排序的程序活动没有改变,JVM可能因为性能原因重排序指令)。指令可以在前和后重排序,但是volatile关键字的读或者写不会跟这些指令混合。无论跟随一个volatile变量的读或者写的指令是什么,都会保证读或者写的前后顺序。
Thread A:
sharedObject.nonVolatile = 123;
sharedObject.counter = sharedObject.counter + 1;
Thread B:
int counter = sharedObject.counter;
int nonVolatile = sharedObject.nonVolatile;因为在写这个volatile的counter之前,线程A写了非volatile得nonVolatile变量,然后当线程A写这个counter(volatile变量)的时候,非volatile得变量也被写回到了主内存中。
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方法获取这个对象。这个类可以工作的很好通过使用一个volatile变量(没有使用synchronized锁),只要只是线程A调用put方法,线程B调用take方法。
while(hasNewObject) {
//wait - do not overwrite existing new object
}
hasNewObject = true; //volatile write
object = newObject;注意这个volatile变量的写是在新的对象被真实赋值之前执行的。对于JVM这个可能看起来是完全正确的。这两个写的执行的值不会互相依赖。
sharedObject.nonVolatile1 = 123;
sharedObject.nonVolatile2 = 456;
sharedObject.nonVolatile3 = 789;
sharedObject.volatile = true; //a volatile variable
int someValue1 = sharedObject.nonVolatile4;
int someValue2 = sharedObject.nonVolatile5;
int someValue3 = sharedObject.nonVolatile6;JVM可能会重排序前面的三个指令,只要他们中的所有在volatile写执行发生前(他们必须在volatile写指令发生前执行)。
翻译地址:http://tutorials.jenkov.com/java-concurrency/volatile.html

本文深入解析Java的volatile关键字,包括其作用原理、可见性保证、保证前后顺序特性以及性能考量等核心内容。
24万+

被折叠的 条评论
为什么被折叠?



