volatile 关键字
译自 http://www.javamex.com/tutorials/synchronization_volatile.shtml
一、简介
volatile在JAVA5开始变化比较大。
volatile是用来说明变量的值会被多个线程修改到,用volatile关键字定义了的变量意味着:
1.这个变量的值不会被线程cache到,所有的读写操作都是直接操作主存
2.访问这个变量的操作就好象是在synchronized block中一样,synchronized(this object)。当然实际上是并没有lock的。
我们来比较一下synchronized和volatile
characteristic | synchronized | volatile |
变量类型 | 对象 | 对象 或 基本类型(指int,long,float..) |
NULL适用么? | 否(会抛出NullPointerException) | 是 |
会阻塞么? | 是 | 否 |
All cached variables synchronized on access? Yes From Java 5 onwards 在访问的时候所有cache的变量都同步么? | 是 | 在JAVA5前/后? |
什么时候进行同步? | 在你明确地进入或离开一个synchronized块时 | 任何时候访问volatile变量 |
可以用来组合多个操作成为一个原子操作? | 是 | 在JAVA5之前不行,JAVA5之后可以 |
二、一个volatile的例子
volatiel一个典型的应用就是拿来做停止线程的flag变量
public class StoppableTask extends Thread {
private volatile boolean pleaseStop;
public void run() {
while (!pleaseStop) {
// do some stuff...
}
}
public void tellMeToStop() {
pleaseStop = true;
}
}
pleaseStop这个变量如果没有定义为volatile,那么线程很可能会把这个变量的值在loop开始的时候就cache在寄存器中,然后一直也不会再去读取,这是很危险的事情。
三、JAVA5中的volatile关键字
在JAVA5中,在访问volatile变量时,会create一个内存屏障(memory barrier),会在主存中同步所有这个变量的实例(synchronizes all cached copies of variables with main memory)。
read after write
先看一个经典的应用,Fixing Double-checked Locking(DCL)
在单例模式设计中,采用lazy initialisation
public class MyFactory {
private static MyFactory instance;
public static synchronized MyFactory getInstance() {
if (instance == null)
instance = new MyFactory();
return instance;
}
private MyFactory() {}
}
这种写法的缺点是 getInstance方法调用会比较慢,因为要同步,所以有些人会这样写来改进:
public class MyBrokenFactory {
private static MyFactory instance;
private int field1, field2 ...
public static MyBrokenFactory getFactory() {
// This is incorrect: don't do it at home, kids!
if (instance == null) {
synchronized (MyBrokenFactory.class) {
if (instance == null)
instance = new MyFactory();
}
}
return instance;
}
private MyBrokenFactory() {
field1 = ...
field2 = ...
}
}
但这样是有问题的:
Thread 1: 'gets in first' and starts creating instance. | Thread 2: gets in just as Thread 1 has written the object reference to memory, but before it has written all the fields. |
1. Is instance null? Yes. 2. Synchronize on class. 3. Memory is allocated for instance. 4. Pointer to memory saved into instance. 7. Values for field1 and field2 are written to memory allocated for object. | 5. Is instance null? No. 6. instance is non-null, but field1 and field2 haven't yet been set! This thread sees invalid values for field1 and field2! |
解决办法,要么用synchronized,要么不要用lazy-initialization,直接构造好instance好了,要么就是用volatile来修饰instance这个变量。
public class MyFactory {
private static volatile MyFactory instance;
public static MyFactory getInstance(Connection conn)
throws IOException {
if (instance == null) {
synchronized (MyFactory.class) {
if (instance == null)
instance = new MyFactory(conn);
}
}
return instance;
}
private MyFactory(Connection conn) throws IOException {
// init factory using the database connection passed in
}
}
这样就正确了,因为JAVA5为volatile带来的特性,保证unsycnrhonized volatile的读操作一定发生在写操作之后,这样读线程一定会看到MyFactory的各个field的正确的值。
还有一个解决办法是把MyFactory中的各个fields都定义成final,final的值必须在构造函数中赋值的。在JAVA5对final有了点修改,就是JVM会保证在对象引用到这块内存的时候,这些值已经提交到主存中。
我想意思大概是,一般是先分配内存,然后用指针记录这块内存,然后赋值给各个fields,但对final的field不同,需赋值之后,才把内存地址赋值给指针。
这里就可以理解为什么在ConcurrentHashMap中的HashEntry的几个fields,value是volatile的,其他的都是final的了。
四、JAVA5中volatileb的变量还有什么新的功能?
1.允许真正的get-and-set原子操作:
AtomicIntegerArray, AtomicLongArray,AtomicReferenceFieldUpdater
2.有效地访问一个volatile数组中的某个元素,而且提供在这个元素上的get-and-set院子操作
AtomicIntegerArray, AtomicLongArray and AtomicReferenceArray
这些特性是隐藏在sun.misc.Unsafe这个类中的,但以各种concurrent类的形式提供给开发人员。