Java中的volatile关键字能够保证每次对变量的读操作直接从主存中读取,而非从Cache中读取;每次写操作都立即将Cache中数据同步到主存。
在多线程环境中多个线程同时访问同一个非volatile变量,并且没有使用锁同步控制,那么运行在不同CPU上的多个线程都会从主存中读取一份变量的副本到Cache中,进而对Cache中的数据进行操作。不同线程中的操作对于彼此来说是不完全可见的,也就是说某个线程修改了变量的值,只是保存在Cache中,可能没有同步会主存,那么另一个线程对此修改不可见;或者一个线程的修改已经同步回主存,而另一个线程仍是从Cache中读取,也没有读取到最新值。
定义为volatile的变量,当某个线程对其执行写操做,Cache中的数据会立马被写会主存,并且会引起其他CPU中对此数据的缓存失效,这样当其他线程在读取此变量时,就会从新从主存中读取,得到了最新的值。
volatile变量仅能保证变量的可见性,而不能变量修改的原子性。即使变量i是volatile的,i++这样的操作在多线程环境中仍然得不到正确的结果。当两个线程同时从主存中读取i的值,然后在CPU中进行加1,这个时候,每个线程得到的值都是i+1,当两个线程均将得到的值写会主存时,结果就是i+1,而不是期望的i+2。
public class ThreadDemo { private static volatile int count = 0; // volatile静态变量 private static class Increment implements Runnable { @Override public void run() { for (int i = 0; i < 100000; i++) // 对count自增10w次 count++; } } public static void main(String[] args) throws Exception { Increment increment = new Increment(); // 开启两个线程对count进行自增10w次操作,期望的到20w Thread t1 = new Thread(increment); Thread t2 = new Thread(increment); t1.start(); t2.start(); t1.join(); // 等待线程结束 t2.join(); // 等待线程结束 System.out.println(count); // 某次输出:162867,结果远偏离期望的20w } }
出于性能考虑,JVM可能会对代码进行重排序操作,并且程序的结果不会受到重排序的影响。这在单线程环境中没有任何问题,但是在多线程环境中,重排序可能会导致不可预估的结果。
public class ThreadDemo { private static boolean prepared = false; // 非volatile的 private static Object product = null; private static class Produce implements Runnable { @Override public void run() { // 此处可能发生重排序,(1)和(2)调换了顺序 product = new Object(); // (1) prepared = true; // (2) } } private static class Consumer implements Runnable { @Override public void run() { // 当(1)和(2)发生了指令重排序,会导致还没有真正创建product,此线程就 // 误认为product已经准备好了。此处还存在可见性问题。 while (!prepared) { // 等待 } Object o = product; } } public static void main(String[] args) throws Exception { new Thread(new Produce()); new Thread(new Consumer()); }
而volatile能够避免指令重排序带来的弊端,在volatile变量操作之前的指令重排序不会被排序到volatile变量操作之后,并且在volatile变量操作之后的指令重排序不会被排序到volatile变量操作之前。也就是说:(1)和(3)处的代码可能会发成重排序,而(1)处代码绝不会被排序到(2)之后,(3)处代码绝不会被排序到(2)之前。
someVariable1 = 1; // (1)
someVariable2 = 2; // (1)
volatileVariable = true; // (2) volatile变量操作
otherVariable1 = 3; // (3)
otherVariable2 = 4; // (3)
volatile可用于状态标识,如布尔类型,当到达一定条件,某个线程修改它的值,其他线程读取此值判断是否执行后续步奏。
public class ThreadDemo { private static volatile boolean prepared = false; // volatile的 private static Object product = null; private static class Produce implements Runnable { @Override public void run() { product = new Object(); prepared = true; // 创建完product,会立即将prepared的值写入主存 } } private static class Consumer implements Runnable { @Override public void run() { while (!prepared) { // 每次判断从主存读取prepared的值,能够及时读取到prepared的最新状态 // 等待 } Object o = product; } } public static void main(String[] args) throws Exception { new Thread(new Produce()); new Thread(new Consumer()); } }
class CheesyCounter { private volatile int value; // volatile变量的读开销接近非volatile变量的读开销 public int getValue() { return value; } // volatile保证每次读取都能够读取到最新值 public synchronized int increment() { // 对写入操作进行同步,确保线程安全 return value++; } }
推荐阅读:
http://tutorials.jenkov.com/java-concurrency/volatile.html
https://www.ibm.com/developerworks/cn/java/j-jtp06197.html
http://www.infoq.com/cn/articles/ftf-java-volatile#anch83440