当一个变量被volatile修饰后,会具备两种含义:
1、保证了不同线程对该变量进行操作时的可见性。
2、禁止进行指令重排序
可见性在这里就不做解释了,网上有很多,解释都很到位,有需要了解的可以百度一下。
这里说一下“原子性”,看如下摘抄代码:
public class Test {
public volatile int inc = 0;
public void increase() {
inc++;
}
public static void main(String[] args) {
final Test test = new Test();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}
while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println(test.inc);
}
}
上边的一段代码,包括对inc的读取、操作、赋值三个操作,但其最后输出的值可能并不一样,也可能不是10000,why?
按理说已经保证了inc变量的可见性,双循环每次取值应该都是最新的,10*1000=10000,这很科学啊!
有没有可能自增操作的三个子操作会分割开执行,如果有就可能导致下面这种情况出现:
假如说线程1在做了i+1,但未赋值的时候,线程2就开始读取i,那么当线程A赋值i=1,并回写到主内存,而此时线程B已经不再需要i的值了,而是直接交给处理器去做+1的操作,于是当线程2执行完并回写到主内存,i的值仍然是1,而不是预期的2。
我的理解是,volatile缩短了普通变量在不同线程之间执行的时间差,但仍然存有漏洞,依然不能保证原子性。
同时在这里提一下volatile的使命,创造它的背景就是在某些情况下可以代替synchronized实现可见性,同时规避synchronized带来的线程挂起、调度的开销的目的。如果volatile也能保证同步,那么它就是个锁,可以完全取代synchronized了。
从这点看,volatile不可能保证同步,也正基于上面的原因,随着synchronized性能逐渐提高,volatile逐渐退出历史舞台。
接下来再说一下什么是禁止重排序,这里先说一下什么是重排序,个人理解是,写在后面的代码在时间顺序上可能会先执行,而写在前面的代码会后执行,从而尽可能的充分利用CPU,我们实际应用中,重排序在多线程中出现的几率比较大。
但由于在关键字上有volatile和synchronized可禁用重排序,还有一些规则也是可以禁止重排序的,通过这些使我们在平时在工作中很少能感觉到重排序的体现。
总结一下:
volatile关键字保证了线程间共享变量的及时可见性,但在其整个执行过程并不保证同步,不保证其原子性,但会保证指令不会重新排序。
但
建议远离volatile,volatile是在synchronized性能低下的时候提出的。如今synchronized的效率已经大幅提升,所以volatile存在的意义不大;并且如今非volatile的共享变量,在访问不是超级频繁的情况下,已经和volatile修饰的变量有同样的效果了。