关键字volatile是java虚拟机提供的最轻量级的同步机制。但是许多人对它的理解还是不正确,不完整。
下面详细讲解一下:
由于关键字volatile关键没有被更好的理解,因此许多程序员遇到处理多线程数据竞争问题的时候都是有synchronzied来同步,仅为读写一两个实例域就使用同步,显得开销有些大了。因此需要深入理解此关键字。
Java内存模型对volatile专门定义了一些特殊的访问规则
当一个变量被定义为volatile时,此变量具备两种特性:
1.可见性:可见性在java内存模型中有定义,可以参看。
普通变量则没有,他们在线程之间的交互是通过主内存来完成,volatile变量则是通过主内存完成交换,但是两者区别在于volatile变量能立即同步到主内存中,当一个线程修改变量的变量的时候,立刻会被其他线程感知到。
特别注意一点:volatile变量的可见性经常性被误解,认为,valotile变量在各个线程中是一致的。所以基于volatile变量是安全的。这种认为是错误的。论据是正确的,但是得出的是安全的就不正确了。不会存在不一致性问题(在各个的工作内存中可以存在不一致的情况,但是由于每次使用之前都要刷新,执行引擎看不到不一致的问题,因此认为不存在不一致的问题)但是java里面的运算中并非原子操作,导致volatile变量的运算在并发下一样不安全。
实现可见性方式: 1.volatile 2.synchronized 3.final
下面代码演示不安全的情况:
public class VolatileTest {
private static final int THREAD_NUM =20;
public static volatile int num= 0;
public static void increase(){
num++ ;
}
public static void main(String[] args) {
Thread[] threads = new Thread[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<1000;i++){
increase();
}
}
});
threads[i].start();
}
while(Thread.activeCount()>1){
Thread.yield();
}
System.out.println(num);
}
}
输出的正确答案应该是200000,但是每次输出都小于200000,问题在于increase()方法。我们用javap发编译看一下发现就increase()方法在Class中文件有四条字节码组成。但是这种字节码测试并不能说明这条指令是原子操作,因为一条字节码指令解释执行时,解释器将要运行许多行代码才能实现它的语义,当编译执行时,一条也可能被住转化为若干本地机器指令。
当不符合一下规则是还是使用synchronized或java.util.concurrent中的原子类。
1.运算结果并不依赖变量的当前值,或者能够确保单一的线程修改变量的
2.变量不需要与其他的状态变量共同参与不变约束。
2.禁止指令重排序优化
普通变量仅仅会保证在该方法的执行过程中所依赖复制结果的地方都能获取到正确的结果,而不能保证变量复制操作的顺序与程序代码中的执行顺序一致。