volatile可以保证变量的可见性
当一个变量定义为volatile后,此变量对所有的线程具有可见性。这里的可见性是指当一个线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。
每次使用volatile变量前都必须先从主内存刷新最新的值,这保证能看见其他线程对变量所做的修改后的值。每次修改volatile变量后都必须同步回主内存,这保证其他线程可以看到自己对变量所做的修改。正是这两条规则保证了volatile变量的可见性。
volatile要求线程每次从共享内存中读取变量,而不是私有内存中读取。
volatile可以保证有序性
volatile可以禁止指令重排序优化,重排序是指编译器和处理器为了优化程序性能而对指令序列进行排序的一种手段。但是重排序也需要遵守一定规则,重排序操作不会对存在数据依赖关系的操作进行重排序。比如:a=1;b=a; 这个指令序列,由于第二个操作依赖于第一个操作,所以在编译时和处理器运行时这两个操作不会被重排序。但是在多线程环境下,可能出现问题。如下示例:
Map configOptions; volatile boolean initialized = false; // 以下代码在线程A中执行 // 线程A完成配置信息后,initialized改为true configOptions = new HashMap(); processConfigOptions(configOptions); initialized = true; // 以下代码在线程B中执行 // 等待initialized为true,表示线程A已完成配置 while (!initialized) { sleep(); } // 线程A完成配置后,线程B可以开始doSomething(); doSomething();
initialized必须定义为volatile,否则可能出现线程A先赋值initialized为true,而后执行配置。
volatile保证了在赋值initialized之前,先执行完其前面的代码。
volatile 性能
volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。
volatile在并发下运算是不安全的,不能保证原子性
如下示例,20个线程分别将变量race自加100次,运算结果race的值小于20000 。
public class VolatileTest { public static volatile int race; public static void increase() { race++; } private static final int threads_count = 20; public static void main(String[] args) { Thread[] threads = new Thread[threads_count]; for (int i=0; i<threads_count; i++) { threads[i] = new Thread( new Runnable() { @Override public void run() { for (int i=0; i<100; i++) increase(); } } ); threads[i].start(); } System.out.println(race); } }
示例中,race++不是一个原子操作,其步骤分解如下:
1)从内存中取出race的值;
2)计算race的新值(加1);
3)将race的值写入内存中。
race是volatile修饰的,这保证了其可见性,即所有的线程在使用该变量时,都是从共享内存中读取。
但volatile不保证原子性,一个线程在进行第二步操作时,其它线程可以读取或修改race的值,这就出现了不同步的现象。
所谓原子性,是指一个操作或多个操作要么全部执行完成且执行过程不被中断,要么就不执行。例如,若race++是原子性的,某个线程在执行该操作时,其他线程不能干涉,必须等待其执行完毕。