Java语言中使用volatile关键字实现了一种弱同步机制。大多数场景下使用volatile变量的同步开销要比使用锁来的低,当一个变量被声明为volatile后,它将具备两种特性:
1、保证被声明volatile的变量对所有线程的可见性,这种“可见性”是指当一个线程修改了这个变量,新值对其它线程是可以立即得知的。但要注意,如果对volatile变量进行非原子操作也会引发安全性问题。
public class VolatileTest1 {
private static final int THREADS_COUNTS = 20;
public static volatile int race = 0;
public static void increase() {
race++;
}
//main
public static void main(String args[]) {
Thread[] threads = new Thread[THREADS_COUNTS];
//创建20个线程,每个线程对increase()方法调用1000次
for (int i = 0; i < THREADS_COUNTS; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
increase();
}
}
});
threads[i].start();
}
while(Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(race);
}
}
上面这个例子期望的结果是200000,但运行程序并不能获得期望结果。原因就出现在“race++”上。由于这个自增运算不是原子操作,所以多个线程运行increase()方法时可能会对已经过期的数据进行操作,导致最终出现意外的结果。
对于volatile变量的一种典型用法是:检查某个状态标记以判断是否退出循环
volatile boolean shutdown;
public void shutdown() {
shutdown = true;
}
public void doWork() {
while(!shutdown) {
//do somethings ...
}
}
2、禁止指令重排序优化。
/**
* 此例子在client模式下从运算结果来看说明不了重排序的问题,
* 这里只是用程序逻辑还有主线程和子线程的关系
* 来说明重排序可能带来的影响。
*
*/
public class VolatileTest2 extends Thread{
// private boolean volatile initialized;
private boolean initialized;
/**
* 初始化任务
* @throws InterruptedException
*/
public void init() throws InterruptedException {
System.out.println("start init ...");
Thread.sleep(10000); //模拟一个长时间的初始化操作
System.out.println("end init ...");
//下面这行代码可能被提前执行,导致初始化工作还未完成,
//可能就开始了运算任务。如果initialized被声明为volatile
//则不会有此问题
initialized = true;
}
public void run() {
while(!initialized) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("start my works ...");
//do somethings ...
}
public static void main(String args[]) throws InterruptedException {
VolatileTest2 vt = new VolatileTest2();
vt.init(); //主线程执行初始化操作
vt.start(); //启动一个子线程,执行运算
}
}
参考:《深入理解Java虚拟机:JVM高级特性与最佳实践 》
《Java并发编程实践》