以下内容大多来自周志明的《深入理解Java虚拟机》。
当一个变量被volatile修饰后,它将具备两种特性:
1. 保证此变量对所有线程的可见性,这里的“可见性”是指当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。而普通变量不能做到这一点,普通变量的值在线程间传递均需要通过主内容来完成,例如,线程A修改了一个普通变量的值,然后向主内容进行回写,另外一条线程B在线程A回写完成之后再从主内存进行读取操作,新变量值才会对线程B可见。
volatile变量在各个线程的工作内存中不存在一致性问题(在各个线程的工作内存中,volatile变量也可以存在不一致的情况,但由于每次使用之前都要先刷新,执行引擎看不到不一致的情况,因此可以认为不存在一致性问题),但是java里面的运算并非原子操作,导致volatile变量的运算在并发下一样是不安全的,看下面代码:
public static volatile int race = 0; public static void increase() { race++; } private static final int ThreadsCnt = 20; public static void main(String[] args) { Thread[] threads = new Thread[ThreadsCnt]; for (int i = 0; i < ThreadsCnt; i++) { threads[i] = new Thread(new Runnable() { @Override public void run() { for (int j = 0; j < 10000; j++) { increase(); } } }); threads[i].start(); } while (Thread.activeCount() > 1) { Thread.yield(); } System.out.println(race); }
20个线程,每个线程对race进行1000次运算,但是结果却不是20000,每次运行的结果都不同。
由于volatile变量只能保证可见性,在不符合一下两条规则的运算场景中,我们仍然需要通过加锁(如synchronized)来保证原子性。
a. 运算结果不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。
b. 变量不需要与其他的状态变量共同参与不变约束。
volatile比较合适的使用场景:
volatile boolean shutDownStatus; public voit shutDown() { shutDownStatus=true; } public void work() { while(!shutDownStatus) { //do something } }
2. 第二个语义:禁止指令重排序优化,普通的变量仅仅会保证在该方法的执行过程中所有依赖复制结果的地方都能获取到正确的结果,而不能保证变量赋值操作的顺序与程序代码中的执行顺序一致。
总结:volatile是轻量级的同步机制,效率可能会比synchronized高一点,但是也有较大的区别:
volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住.
volatile仅能使用在变量级别,synchronized则可以使用在变量,方法.
volatile仅能实现变量的修改可见性,但不具备原子特性,而synchronized则可以保证变量的修改可见性和原子性.
volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞.
volatile标记的变量不会被编译器优化,而synchronized标记的变量可以被编译器优化.
volatile参考资料:
http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html
http://blog.csdn.net/fanaticism1/article/details/9966163