volatile這個關鍵字在Java 5之前,它是一個備受爭議的關鍵字,因為在程序中使用它往往會導致出人意料的結果。在Java 5之後,volatile關鍵字才得以重獲生機。
volatile關鍵字雖然從字面上理解起來比較簡單,但是要用好不是一件容易的事情。
被volatile修飾的變量,將具備兩種特性:
1)保證此變量對所有的線程的可見性。
這裡的“可見性”,指的是當一個線程修改了這個變量的值,volatile 保證了新值能立即同步到主內存,保證其他線程得到的變量是最新的值。普通變量是做不到這點的。
如果要理解這一點需要對程序執行的過程有所瞭解。
當程序在運行過程中,會將運算需要的數據從主存複製一份到CPU的高速緩存當中,那麼CPU進行計算時就可以直接從它的高速緩存讀取數據和向其中寫入數據,當運算結束之後,再將高速緩存中的數據刷新到主存當中。
如下面的這段代碼:
<code>a = a + 1;/<code>
這個代碼在單線程中運行是沒有任何問題的,但是在多線程中運行就會有問題了。
在多核CPU中,每條線程可能運行於不同的CPU中。如果有兩個線程同時執行以上代碼,線程1執行 a =a+1這句時,會先把a的初始值加載到CPU1的高速緩存中,然後加1。同樣線程2執行 a =a+1這句時,也會先把a的初始值加載到CPU2的高速緩存中,然後加1。這樣可能最總a的值只增加了1,並沒有增加了我們預想中的2。
volatile修飾的變量,會保證修改的值會立即被更新到主存,當有其他線程需要讀取時,會去主內存中讀取新值。就避免了以上的問題。
2)禁止指令重排優化。
指令重排指的是處理器為了提高程序運行效率,可能會對輸入代碼進行優化,重新排序代碼的執行順序,它不保證程序中各個語句的執行先後順序同代碼中的順序一致,但是它會保證程序最終執行結果和代碼順序執行的結果是一致的。
原理是,編譯器和處理器在重排序時,會遵守數據依賴性,編譯器和處理器不會改變存在數據依賴關係的兩個操作的執行順序。
例如代碼在線程1執行:
<code>loadconfig(); //語句1/<code>
以上語句因為是沒有數據依賴性的,所以CPU可能會對他們的執行順序進行重新排序。可能會先執行語句2然後執行語句1。
而線程2:
<code>While(!isInit){/<code>
以上代碼,會導致因為線程1先執行語句2,isInit的值為true,線程2則不再等待配置初始化往下運行。導致程序出現處理邏輯錯誤。
值得注意的一點,volatile雖然能夠保證線程的可見性 但是並不能保證原子性,所以不能替代synchronized。
閱讀更多 IT技術研習社 的文章