為什麼volatile也無法保證執行緒安全
Java記憶體模型
java使用的是共享變數模型,如下圖所示
執行緒1要讀取執行緒2修改後的值必須要執行緒2寫回到記憶體,執行緒1再讀取。
Jvm又是如何讀取主存變數到執行緒中的呢?
記憶體間的相互操作
lock 將物件變成執行緒獨佔的狀態
unlock 將執行緒獨佔狀態的物件的鎖釋放出來
read 從主記憶體讀資料
load 將從主記憶體讀取的資料寫入工作記憶體
use 工作記憶體使用物件
assign 對工作記憶體中的物件進行賦值
store 將工作記憶體中的物件傳送到主記憶體當中
write 將物件寫入主記憶體當中,並覆蓋舊值
Volatile語義
Volatile的第一個語義就是保證此執行緒的可見性,一個執行緒對此變數的更改其他執行緒是立即可知的。也就是說 assign,store,write這三個操作是原子的,中間不會中斷,會馬上同步回主存,就好像直接操作主存一樣,並通過快取一致性通知其他快取中的副本過期。普通變數可能會在
其他快取?過期?
Cpu與記憶體資料讀取
在多核cup時代,不同執行緒可能在不同cup的核心中執行,由於cup處理速度和記憶體的讀取速度大概相差大約一百倍,為了讓cpu效能不浪費,cpu中做了一個快取記憶體,cpu在處理的時候會把一批可能用到的資料載入到快取中,等執行完畢再寫回記憶體,cpu與記憶體的架構圖如下
Java記憶體模中的資料型與cup快取與主存的對應關係如下
共享記憶體的變數與執行緒棧中的變數副本有可能在主存中,也有可能在cpu快取中或者cpu暫存器中。
Volatile對應的執行程式碼
看下邊程式碼
Java程式碼: instance = new Singleton();//instance是volatile變數
彙編程式碼: 0x01a3de1d: movb $0x0,0x1104800(%esi);0x01a3de24: lock addl $0x0,(%esp);
有volatile修飾的共享變數進行寫操作的時候會多第二行彙編程式碼,查IA-32架構軟體開發者手冊可知,lock字首的指令在多核處理器下會引發了兩件事情。
1.將當前處理器快取行的資料會寫回到系統記憶體(快取行概念請自行查詢)。
2.這個寫回記憶體的操作會引起在其他CPU裡快取了該記憶體地址的資料無效
快取一致協議
怎麼理解這個快取無效,這就得說說快取一致協議(MESI協議)(涉及到快取行的概念,請自行查詢)
cache狀態
描述
M(Modified)
這行資料有效,快取資料被修改了,和記憶體中的資料不一致,資料只存在於本Cache中。
E(Exclusive)
這行資料有效,快取資料和記憶體中的資料一致,資料只存在於本Cache中。
S(Shared)
這行資料有效,快取資料和記憶體中的資料一致,資料存在於很多Cache中。
I(Invalid)
快取資料無效。
cache操作
快取一致協議(MESI)協議中,每個cache的控制器不僅知道自己的操作,也通過監聽知道其他CPU中cache的操作;
local read(LR):表示本核心讀本Cache中的值;
local write(LW):表示本核心寫本Cache中的值;
remote read(RR):表示其它核心讀其它Cache中的值;
remote write(RW):表示其它核心寫其它Cache中的值;
MESI狀態之間的變化過程如下:
當前狀態 |
事件 |
行為 |
下一個狀態 |
I(Invalid) |
Local Read |
如果其它Cache沒有這份資料,本Cache從記憶體中取資料,Cache line狀態變成E; 如果其它Cache有這份資料,且狀態為M,則將資料更新到記憶體,本Cache再從記憶體中取資料,2個Cache 的Cache line狀態都變成S; 如果其它Cache有這份資料,且狀態為S或者E,本Cache從記憶體中取資料,這些Cache 的Cache line狀態都變成S |
E/S |
Local Write |
從記憶體中取資料,在Cache中修改,狀態變成M; 如果其它Cache有這份資料,且狀態為M,則要先將資料更新到記憶體; 如果其它Cache有這份資料,則其它Cache的Cache line狀態變成I |
M |
|
Remote Read |
既然是Invalid,別的核的操作與它無關 |
I |
|
Remote Write |
既然是Invalid,別的核的操作與它無關 |
I |
|
E(Exclusive) |
Local Read |
從Cache中取資料,狀態不變 |
E |
Local Write |
修改Cache中的資料,狀態變成M |
M |
|
Remote Read |
資料和其它核共用,狀態變成了S |
S |
|
Remote Write |
資料被修改,本Cache line不能再使用,狀態變成I |
I |
|
S(Shared) |
Local Read |
從Cache中取資料,狀態不變 |
S |
Local Write |
修改Cache中的資料,狀態變成M, 其它核共享的Cache line狀態變成I |
M |
|
Remote Read |
狀態不變 |
S |
|
Remote Write |
資料被修改,本Cache line不能再使用,狀態變成I |
I |
|
M(Modified) |
Local Read |
從Cache中取資料,狀態不變 |
M |
Local Write |
修改Cache中的資料,狀態不變 |
M |
|
Remote Read |
這行資料被寫到記憶體中,使其它核能使用到最新的資料,狀態變成S |
S |
|
Remote Write |
這行資料被寫到記憶體中,使其它核能使用到最新的資料,由於其它核會修改這行資料, 狀態變成I |
I |
此表為引用《大話大話處理器》,如有侵權請聯絡刪除
為什麼i++不是執行緒安全的
為什麼多執行緒中i++不是執行緒安全的,首先++操作不是原子的,要經過讀取計算和寫回
3和4是連續操作不間斷,5,6,7也是連續不間斷的,6把舊值覆蓋了新值。
這就是為什麼volatile定義的變數在多執行緒做++操作時也是執行緒不安全的原因。
語義1總結
簡而言之就是用關鍵字修飾的就是修改後及時寫回主存,對其他執行緒可見可理解為其他執行緒探嗅到自己快取中的變數是過期的(不同執行緒在同一核心道理相同)。
Volatile語義2
Volatile第二層意思就是禁止指令重排序。
Jvm為了優化效能會採用指令重排序
Int a=1;
Int b=2;
Jvm的執行順序可能是先b=2;然後再a=1,因為當代碼執行到a=1時a物件可能被加鎖了,這時如果等待鎖釋放顯然浪費了時間,所以先執行b=2,但是用volatile修飾的關鍵字在操作的時候必須是在指定位置的,即如果這個變數的操作在第五行那會保證前四行都執行完才會執行第五行。
------------------------------------------------------------------------------------------------------更多精彩內容請關注微信公眾號
IT農廠【ITFF01】