1. 程式人生 > >為什麼volatile也無法保證執行緒安全

為什麼volatile也無法保證執行緒安全

Java記憶體模型

java使用的是共享變數模型,如下圖所示

 

執行緒1要讀取執行緒2修改後的值必須要執行緒2寫回到記憶體,執行緒1再讀取。

Jvm又是如何讀取主存變數到執行緒中的呢?

記憶體間的相互操作

lock 將物件變成執行緒獨佔的狀態

unlock 將執行緒獨佔狀態的物件的鎖釋放出來

read 從主記憶體讀資料

load 將從主記憶體讀取的資料寫入工作記憶體

use 工作記憶體使用物件

assign 對工作記憶體中的物件進行賦值

store 將工作記憶體中的物件傳送到主記憶體當中

write 將物件寫入主記憶體當中,並覆蓋舊值

 

Volatile語義

Volatile的第一個語義就是保證此執行緒的可見性,一個執行緒對此變數的更改其他執行緒是立即可知的。也就是說 assign,store,write這三個操作是原子的,中間不會中斷,會馬上同步回主存,就好像直接操作主存一樣,並通過快取一致性通知其他快取中的副本過期。普通變數可能會在

assign,store,write之間插入其他操作,導致更改後的資料無法馬上同步回主存,其他執行緒讀取的可能是過期的舊資料。

其他快取?過期?

Cpu與記憶體資料讀取

在多核cup時代,不同執行緒可能在不同cup的核心中執行,由於cup處理速度和記憶體的讀取速度大概相差大約一百倍,為了讓cpu效能不浪費,cpu中做了一個快取記憶體,cpu在處理的時候會把一批可能用到的資料載入到快取中,等執行完畢再寫回記憶體,cpu與記憶體的架構圖如下

 

Java記憶體模中的資料型與cup快取與主存的對應關係如下

 

共享記憶體的變數與執行緒棧中的變數副本有可能在主存中,也有可能在cpu快取中或者cpu暫存器中。

Volatile對應的執行程式碼

看下邊程式碼

Java程式碼: instance = new Singleton();//instancevolatile變數

彙編程式碼: 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的控制器不僅知道自己的操作,也通過監聽知道其他CPUcache的操作;

local readLR):表示本核心讀本Cache中的值;

local writeLW):表示本核心寫本Cache中的值;

remote readRR):表示其它核心讀其它Cache中的值;

remote writeRW):表示其它核心寫其它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++不是執行緒安全的,首先++操作不是原子的,要經過讀取計算和寫回

 

34是連續操作不間斷,567也是連續不間斷的,6把舊值覆蓋了新值。

這就是為什麼volatile定義的變數在多執行緒做++操作時也是執行緒不安全的原因。

語義1總結

簡而言之就是用關鍵字修飾的就是修改後及時寫回主存,對其他執行緒可見可理解為其他執行緒探嗅到自己快取中的變數是過期的(不同執行緒在同一核心道理相同)。

Volatile語義2

Volatile第二層意思就是禁止指令重排序。

Jvm為了優化效能會採用指令重排序

Int a=1;

Int b=2;

Jvm的執行順序可能是先b=2;然後再a=1,因為當代碼執行到a=1a物件可能被加鎖了,這時如果等待鎖釋放顯然浪費了時間,所以先執行b=2,但是用volatile修飾的關鍵字在操作的時候必須是在指定位置的,即如果這個變數的操作在第五行那會保證前四行都執行完才會執行第五行。

------------------------------------------------------------------------------------------------------更多精彩內容請關注微信公眾號 IT農廠【ITFF01】