附2 volatile,volatile
註:在閱讀本章之前,先要瞭解Java記憶體模型,見上一章《附1 Java記憶體模型與共用變數可見度》,連結如下:
http://www.cnblogs.com/java-zhao/p/5124725.html
1、volatile用法
具體的用法可以參照《第二章 ConcurrentHashMap源碼解析》中的Segment內部類的count屬性,可以看看多線程情況下怎樣對其進行操作的,具體連結如下;
http://www.cnblogs.com/java-zhao/p/5113317.html
或者可以參照《第三章 CopyOnWriteArrayList源碼解析》中CopyOnWriteArrayList的底層數組,具體連結如下;
http://www.cnblogs.com/java-zhao/p/5121944.html
2、具體的實現原理
- 對volatile變數執行寫操作時,會在寫操作後加入一條store屏障指令
- 對volatile變數執行讀操作時,會在讀操作前加入一條load屏障指令
說人話:
- 對volatile變數執行讀操作時,都要強制的先從主記憶體讀取最新的變數值到工作記憶體,然後再讀工作記憶體中所儲存的變數副本
- 對volatile變數執行寫操作時,又會強制的將工作記憶體中的剛剛改變的值寫到主記憶體中去
通過上邊這樣模式,每個線程拿到的volatile變數值都是最新的。
注意:
volatile無法實現原子性:
eg.
private volatile int count = 0;
假設現在有兩條線程分別對count執行加1操作,那麼期待的結果最後count==2,但是看下邊的分析:
假設有如下流程:
1)線程a擷取了count==0;
2)線程b擷取了count==0;
3)線程b對count+1,之後寫入主記憶體count==1;
4)線程a對count+1,之後寫入主記憶體count==1;
結果count==1而非count==2,原因就是線程a擷取count後,volatile不能實現原子性,這個時候b也能去操作count。
想要實現原子性,使用synchronized去鎖住增加方法,或者使用ReentrantLock去鎖住增加代碼;當然,以上情境使用AtomicInteger更好。
3、volatile使用情境
- 運算結果並不依賴當前值,例如Boolean就可,而number++這樣的就不行
- 運算結果依賴當前值但是能夠確保只有單一線程修改變數的值,例如ConcurrentHashMap中Segment的count變數
- 變數不需要與其他的狀態變數共同參與不變約束,例如low<up這樣的情境就不行
註:
- 關於volatile禁止指令重排序的介紹去看《深入理解Java虛擬機器(第二版)》第十二章"Java記憶體模型與線程"
- 通常情況下,能用volatile解決的就不去用synchronized了