Volatile 原理,優化,應用詳解

1. volatile 原理

Volatile 是輕量級的synchronized,保證了共享變量的可見性(一個線程修改共享變量,另一個變量能讀到這個修改的值。),volatile不會引起線程上下文的切換和調度,所以比synchronized執行成本低。

volatile 修飾的共享變量,進行寫操作的時候會多出一行lock開頭的彙編指令。Lock前綴的指令做了兩件事:
1)將當前處理器的緩存行的數據寫回系統內存。(此時處理器會獨佔內存)
2)同時使其他cpu裏緩存該內存地址的數據無效。實現緩存一致性協議:每個處理器利用嗅探技術訪問系統內存和其它處理器的內部緩存,當出現數據不一致的時候,就要設置成無效狀態(緩存行失效),然後下次訪問該處理器緩存行的數據時候,需要重新從內存中讀取到處理器緩存中(緩存行填充)。
這裏寫圖片描述
CPU術語的定義
這裏寫圖片描述

2. Volatile 寫讀的內存語義以及實現
線程A寫volatile變量,JMM會把該線程對應的本地內存的共享變量值刷新到主存中。通過主存向接下來要讀這個volatile變量的線程發出消息。
線程B讀一個volatile變量,JMM會把線程對應的本地內存置爲無效,接收之前線程發出的消息,從主存中讀取共享變量,這樣線程B在讀之前的所有寫都是可見的。

實現:編譯器在生成字節碼時,在指令序列中插入內存屏障來禁止處理器指令重排序。
保守插入策略:
1)每個volatile寫前插入StoreStore屏障
2)每個volatile寫後插入StoreLoad屏障
3)每個volatile讀後插入LoadLoad屏障
4)每個volatile讀後插入LoadStore屏障
x86處理器只會對寫-讀操作做重排序,因此,可以省略3種內存屏障,只需要在volatile寫後插入一個StoreLoad屏障即可實現volatile內存語義。volatile寫的開銷大於讀的開銷。

這裏寫圖片描述

3.Volatile的使用優化

LinkedTransferQueue這個類在使用volatile變量的時候,用追加的字節的方式來優化出隊和入隊。
LinkedTransferQueue 用一個內部類來定義隊列的頭結點和尾節點,內部類PaddedAtomicReference追加了15個變量(共60字節),加上父類的value變量,一共64字節。
爲什麼追加字節可以優化呢?
因爲很多處理器的高速緩存行是64字節寬。當字節不足64的時候,處理器會將它們都讀到同一個高速緩存行,所以如果兩個volatile節點同時寫操作就會相互鎖住同一個緩存行。
以下可以不用這種方式:
a.當處理器的高速緩存行不是64字節寬;
b.Volatile變量寫入沒有那麼頻繁。

4.Volatile在雙重檢查的單例模式的使用
雙重檢查的由來。
(1)
這裏寫圖片描述
問題:非線程安全的。造成2重複執行。

(2)
這裏寫圖片描述
問題:synchronized鎖住了整個方法。當訪問線程數多的時候,性能低。
(3)
這裏寫圖片描述
問題:
Instance=new Instance() 創建對象的時候,可以分成
(1)分配對象內存空間
(2)初始化對象
(3)使instance指向剛分配的內存地址。
這裏寫圖片描述
這時(2)和(3)之間可能會發生重排序。因爲在單線程中,(2)(3)重排序也不會影響結果。
而在多線程中,線程1執行new instance()的時候步驟(2)(3)發生重排序。使得instance先指向分配好的內存地址,而沒有初始化對象。此時線程2 執行if(instance==null)判斷不爲空,直接返回instance。
但是這使instance沒有初始化,導致返回錯誤。

(4)
這裏寫圖片描述
這是正確寫法。Volatile 保證new instance()的時候步驟(2)(3)不會發生指令重排序,也就是對象先初始化再將instance的引用指向對象內存地址。


參考: java併發編程的藝術

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