volatile關鍵字介紹
volatile修飾的變量在多處理器開發中保證了共享變量的“可見性”。可見性的意思是當一個線程修改一個共享變量時,另外一個線程能讀到這個修改的值。Java中的volatile關鍵字用作Java編譯器和Thread的指示符,它們不會緩存此變量的值並始終從主存中讀取它。
Java在Java內存模型(JMM)中引入了一些變化,它保證了從一個線程到另一個線程的變化的可見性,也就是“happens-before”在一個線程中發生的內存寫入的問題可能“泄漏”並被另一個線程看到。

圖片來自: http://javarevisited.blogspot.jp/2011/06/volatile-keyword-java-example-tutorial.html
volatile使用要點
Java中的volatile關鍵字保證volatile變量的值總是從主存儲器讀取而不是從線程的本地緩存讀取。
在Java中,對於使用Java volatile關鍵字(包括long和double變量)聲明的所有變量,讀寫操作都是原子性的。
在變量中使用Java中的volatile關鍵字可以減少內存一致性錯誤的風險,因爲對Java中volatile變量的任何寫入與該變量的後續讀取建立了一個happens-before關係。
對於大多數基本變量(除long和double之外的所有類型),即使沒有在Java中使用volatile關鍵字,對於引用變量的讀和寫是原子的。
對Java中volatile變量的訪問從來沒有機會阻塞,因爲我們只做一個簡單的讀或寫,因此與synchronized塊不同,我們永遠不會持有任何鎖或等待任何鎖。
作爲對象引用的Java volatile變量可以爲null。
Java volatile關鍵字並不意味着原子,比如對聲明volatile變量 i++ 操作並不是原子的,使操作原子你仍然需要確保使用synchronized方法或在Java中的塊進行獨佔訪問。
如果變量不在多個線程之間共享,則不需要對該變量使用volatile關鍵字。
與synchronized的區別
Java中的volatile關鍵字是一個字段修飾符,而同步修改代碼塊和方法。
synchronized需要獲取和釋放監視器鎖,而 volatile關鍵字不需要持有鎖。
在Java中的線程可以被阻塞以等待任何監視器在同步的情況下,而Java中的volatile關鍵字不是這樣。
同步方法比Java中的volatile關鍵字影響性能。
由於Java中的volatile關鍵字僅同步線程內存和“主”內存之間的一個變量的值,而同步則同步線程內存和“主”內存之間的所有變量的值,並鎖定和釋放監視器以進行引導。由於這個原因,Java中的synchronized關鍵字很可能比volatile具有更多的開銷。
不能在空對象上同步,但Java中的volatile變量可以爲null。
示例代碼
這裏舉例說明volatile修飾變量的複合操作 i++ 不具有原子性。
public class VolatilePractice {
private volatile int i = 0;
public int get(){
return i; ////單個volatile變量的讀與寫具有原子性
}
public void set(int n){
this.i = n;
}
//如果在方法上加synchronized修飾
public void getAndIncrement(){
i++; //複合(多個)volatile變量的讀/寫不具有原子性
}
public static void main(String[] args){
VolatilePractice volatilePractice = new VolatilePractice();
ExecutorService executorService = Executors.newFixedThreadPool(30);
for(int i = 0; i < 10000; i++){
executorService.execute(new VolatileAtomicity(volatilePractice));
}
executorService.shutdown();
//判斷是否所有線程執行完成
while(executorService.isTerminated()){
System.out.println(volatilePractice.get());
break;
}
}
}
class VolatileAtomicity implements Runnable{
private VolatilePractice volatilePractice;
public VolatileAtomicity(VolatilePractice volatilePractice){
this.volatilePractice = volatilePractice;
}
public void run(){
volatilePractice.getAndIncrement();
}
}
如果 i++ 操作是原子的,正常情況下打印的結果應該是10000,但實際每次的結果大都不同並且小於10000; 如果在 getAndIncrement() 方法上加 synchronized 關鍵字(或者方法內用 lock 等),就能保證該方法操作的原子性了,就會得到輸出值 10000 了。
參考資料
How Volatile in Java works? Example of volatile keyword in Java
Java併發編程:volatile關鍵字解析