Java多線程的volatile底層實現原理

或許你經常被問到?

Volatile關鍵字有何作用?

實現這些作用的底層如何實現?

Volatile能夠保障可見性、有序性?原子性嗎?

前言

我們都知道,Java代碼在編譯後會變成Java字節碼,字節碼被類加載器加載到JVM裏,JVM執行字節碼,最終需要轉化爲彙編指令在CPU上執行,Java中所使用的併發機制依賴於JVM的實現和CPU的指令。

Volatile作用

  Java語言規範第3版中對volatile的定義如下:Java編程語言允許線程訪問共享變量,爲了確保共享變量能被準確和一致地更新,線程應該確保通過排他鎖單獨獲得這個變量。我們看下面的圖來理解。2個CPU都要去操作主存中counter變量時,,他們讀取主存中的變量counter到自己cpu cache中,然後操作數據。CPU1改變counter=7的操作,對CPU2是不可見的。

Java多線程的volatile底層實現原理

實現可見性

上面的例子,如果我們加上Volatile關鍵字,實際上底層是這麼回事。

1)將當前處理器緩存行的數據寫回到系統內存。

2)寫回主存的操作會使在其他CPU裏緩存了該內存地址的數據無效,其他CPU執行時,就需要重新從主存中獲取數據。

實現禁止指令重排序

     禁止指令重排序有沒有什麼例子?可以參考下我的另一篇文章:  DCL的單例一定是線程安全的嗎 

Java內存模型其實是通過內存屏障(Memory Barrier)來實現的禁止指令重排序, 內存屏障之前的所有寫操作都要回寫到主內存,內存屏障之後的所有讀操作都能獲得內存屏障之前的所有寫操作的最新結果(實現了可見性)。

屏障類型 指令示例 說明
LoadLoad Load1; LoadLoad; Load2 保證load1的讀取操作在load2及後續讀取操作之前執行
StoreStore Store1; StoreStore; Store2 在store2及其後的寫操作執行前,保證store1的寫操作已刷新到主內存
LoadStore Load1; LoadStore; Store2 在stroe2及其後的寫操作執行前,保證load1的讀操作已讀取結束
StoreLoad Store1; StoreLoad; Load2 保證store1的寫操作已刷新到主內存之後,load2及其後的讀操作才能執行

比如在對象instance進行寫操作,之前加StoreStore,之後加StoreLoad。

虛擬機廠商的內存屏障(Memory Barrier)技術是遵循MESI協議的。其他不通的虛擬機廠商或許有其他技術,但是也需要遵循MESI協議。

MESI 是指4中狀態的首字母。每個Cache line有4個狀態,可用2個bit表示,它們分別是:

或許你會問爲什麼,這個實際上是主存、跟CPU中高速緩存的一種協議 MESI協議,我拋磚引玉,略說一點,內容很多,想了解的同學,可以自行百度

總結:volatile實現了Java內存模型中的可見性和有序性,它的這兩大特性則是通過內存屏障來實現的,同時volatile無法保證原子性。(因爲有序性是編譯器優化帶來的問題,可見性是緩存不一致帶來的問題,而原子性,是線程切換帶來的問題)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章