volatile
volatile和synchronized關鍵字在併發編程中扮演着非常重要的角色,volatile可以說是輕量級的synchronized,它在多處理器開發中保證了共享變量的可見性(一個線程修改某個變量的值,對於另一個線程是可見的)。
volatile的特性
- volatile禁止指令的重排序
重排序:指編譯器和處理器爲了優化程序性能而對指令序列進行重新排序的一種手段
源代碼從編譯到執行會經歷三類重排序:編譯器重排序、指令集並行重排序、內存系統重排序,但是有些時候指令是不能重排序的,比如存在數據依賴性,JMM採用內存屏障來禁止特殊指令的重排序。下圖是JMM針對編譯器的重排序規則表: - volatile實現共享變量的可見性,如下圖所示的java內存模型,對於共享編程,每個線程都會在本地內存中保留一個副本,線程執行時對副本進行操作,那麼可想而知,如果線程A對共享變量的副本進行修改,線程B是不能得到修改後的值的,但是volatile的寫-讀內存語義保證可以強制將更改後的變量值刷新到共享內存,並使線程B的共享變量副本失效,實際上這也是線程A直接對線程B的通信。
我們要先明白happens-before規則:JDK5開始使用JSR-133內存模型,這個模型採用happens-before的概念操作內存的可見性,這個規則包含以下規則:- 一個線程的操作happens-before這個線程的後續操作
- 監視器鎖規則:對一個鎖的加鎖happens-before與這個鎖的解鎖
- volatile變量規則:對volatile的寫happens-before於後續對這個變量的讀
- 傳遞性:Ahappens-before B,且B happens-before C,則A happens-before C
舉個例子:如下代碼,根據happens-before規則,1 hb 2,3 hb 4 ,根據volatile規則,2 hb 3,根據傳遞性 1 hb 4,但是JSR133之前的模型雖然不讓voltile變量重排序,但是volatile與普通變量可以,就會導致下述代碼發生變化,線程A先執行volatile,然後線程B執行volatile然後讀去a(此時a還未被更改),就破壞了語義。
最後我們對volatile的語義進行驗證:
- 我們先不對共享變量加volatile關鍵字,更改線程A的值,看B是否會得到正確的值
public boolean isBreak = false; //第二步加上volatile的修飾 //public volatile boolean isBreak = false public boolean getBreak() { return isBreak; } public void setBreak(boolean aBreak) { isBreak = aBreak; } public class A extends Thread{ @Override public void run() { System.out.println("loop start"); while (!isBreak){ } System.out.println("loop end"); } } public class B extends Thread{ @Override public void run() { setBreak(true); } } public static void main(String[] args) throws InterruptedException { VolatileTest volatileTest = new VolatileTest(); volatileTest.new A().start(); //進入循環 Thread.sleep(1000); volatileTest.new B().start(); //等待修改的同步 Thread.sleep(1000); System.out.println("isBreak:"+volatileTest.getBreak()); }
結果如下圖A:和我們預料的一樣,線程A沒有得到最新值,說明普通變量不具備可見行,然後添加volatile修飾的結果如圖B所示
- volatile保證單次讀/寫的原子性對單個volatile變量的讀/寫具有原子性: 可以理解爲使用鎖對單個變量的讀寫進行了鎖操作,像a++這樣的操作就不能保證原子性