关键字volatile可以说是java虚拟机提供的最轻量级的同步机制。
当一个变量定义为volatile之后,它就具备两种特性:
1、保证此变量对所有线程的可见性,即当一个线程修改了这个变量的值,新值对于其他线程是立即可得的。
注:关于volatile的可见性经常被开发人员误解,觉得:volatile变量对所有线程是立即可见的,所以volatile变量在各个线程中是一致的,所以基于volatile变量的运算在并发 下是安全的。volatile变量在各个线程的工作内存中不存在不一致的问题(在各个线程的工
作内存中,volatile变量也可以存在不一致的情况,但由于每次使用之前都要 先刷新,执行引擎看不到不一致的情况,因此可以认为不存在不一致的问题) 但是Java里面的运算并非原子操作,导致volatile变量的运算在并发下一样是不安全的。如程序1-1
public class VolatileTest {
public static volatile int race = 0;
public static void increase() {
race++;
}
private static final int THREAD_COUNT = 20;
public static void main(String[] args) {
Thread[] threads = new Thread[THREAD_COUNT];
for(int i=0;i<THREAD_COUNT;i++) {
threads[i] = new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<10000;i++) {
increase();
}
}
});
threads[i].start();
}
while(Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(race);
}
}
程序1-1
如果并发正确,结果应该为200000,但结果是一个小鱼200000的数字。
问题在于race++我们用javap反编译之后,得到程序1-2
程序1-2
可以看出,race++操作分程了4条指令,首先getstatic将race的值取到操作栈顶,volatile保证了race的值此时是正确的,但是执行iconst_1,iadd这些指令的时候,其他线程可能已经把race值加大了,栈顶的值此时已过期,所以putstatic执行后就可能把较小的race值同步回主内存之中。
在不符合一下两条规则的运算场景中,我们仍然要通过加锁来保证原子性
1、运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值
2、变量不需要与其他的状态变量共同参与不变约束
2、第二个语义是禁止指令重排序优化
Map configOptions;
char[] configText;
volatile boolean initialized = false;
class thread1 implements Runnable {
@Override
public void run() {
configOptions = new HashMap<>();
//模拟读取配置信息,当读取完成后将initialized设置为true以通知其他线程配置可用
configText = readConfigureFile();
initialized = true;
}
}
class thread2 implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
while(!initialized) {
//initialized为false的时候睡眠
Thread.sleep();
}
//使用初始化好的配置信息
doSomethingWithConfig();
}
}
//模拟读取配置信息方法
private char[] readConfigureFile() {
//读取配置信息
return null;
}
//模拟使用配置信息的方法
private void doSomethingWithConfig() {
//使用初始化好的配置信息
}
程序2-1
在程序2-1中的语义为执行完线程thread1中的读取配置文件操作后,将initialized状态设置为true,线程thread2判断initialized状态为true的时候,对读取到的配置文件进行处理。而这时候如果没有对initialized变量添加volatile修饰,就可能会由于指令重排序的优化,导致位于thread1中最后一句的initialized=true被提前执行,这样thread2中使用配置信息的代码就可能出现错误,而volatile关键字则可以避免此类情况的发生。