03.04 Volatile、線程可見性、指令重排; 這三者到底有什麼樣的關係?

volatile這個關鍵字在Java 5之前,它是一個備受爭議的關鍵字,因為在程序中使用它往往會導致出人意料的結果。在Java 5之後,volatile關鍵字才得以重獲生機。

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修飾的變量,會保證修改的值會立即被更新到主存,當有其他線程需要讀取時,會去主內存中讀取新值。就避免了以上的問題。

Volatile、線程可見性、指令重排; 這三者到底有什麼樣的關係?

2)禁止指令重排優化。

指令重排指的是處理器為了提高程序運行效率,可能會對輸入代碼進行優化,重新排序代碼的執行順序,它不保證程序中各個語句的執行先後順序同代碼中的順序一致,但是它會保證程序最終執行結果和代碼順序執行的結果是一致的。

原理是,編譯器和處理器在重排序時,會遵守數據依賴性,編譯器和處理器不會改變存在數據依賴關係的兩個操作的執行順序。

例如代碼在線程1執行:

<code>loadconfig();       //語句1/<code>

以上語句因為是沒有數據依賴性的,所以CPU可能會對他們的執行順序進行重新排序。可能會先執行語句2然後執行語句1。

而線程2:

<code>While(!isInit){/<code>

以上代碼,會導致因為線程1先執行語句2,isInit的值為true,線程2則不再等待配置初始化往下運行。導致程序出現處理邏輯錯誤。

值得注意的一點,volatile雖然能夠保證線程的可見性 但是並不能保證原子性,所以不能替代synchronized。


分享到:


相關文章: