2. volatile關鍵字

   一個變量被volatile修飾時,它會保證需要寫回的值會被立即更新到內存,而讀取時也會直接從內存讀取新值。

       volatile關鍵字保證了可見性和一定的有序性,而不保證原子性。

保證可見性


//線程1
boolean stop = false;
while(!stop){
    doSomething();
}

//線程2
stop = true;

這段代碼通過線程2來結束線程1的循環,但會存在一些問題,如果線程2把stop的值修改後並沒有及時寫回,而去做其他事情了。這時候線程1並不知道線程2將stop修改了而會一直循環下去。

但使用了volatile關鍵字修飾stop後就不會有這種問題,因爲線程2對stop的修改會立刻反應到內存上,而線程1對stop的讀取也會直接從內存上。這就保證了可見性。

不保證原子性

可見性只能保證每次讀取的是最新的值,但是volatile沒辦法保證對變量的操作的原子性。

//線程1
inc++;

//線程2
inc++;

假設 i 是volatile修飾的,存在以下情況:

/**
*線程1讀取inc的值後,還沒有操作就被阻塞了。
*線程2被喚醒,從主存讀取inc的值,加1,然後被阻塞。(此時還沒來得及把新的值重新賦值給inc,當然也還沒同步到主存)
*線程1被喚醒,inc值加1,然後同步到主存(線程1結束,使得線程2緩存行無效)
*線程2被喚醒,把最新的值賦值給inc,同步到主存(此時線程2,inc的值在第2步時已經被處理過了,僅僅只是把新的值賦值給inc而已。這個時候是不會再去讀取inc的緩存行的,雖然inc的緩存行此時已經無效了)
*這時候得到的最後結果 i 仍然是1。
**/

也就是說volatile只保證寫回操作是直接寫回到內存中並讓其他線程關於這個變量的緩存失效,然後讓其他線程讀取這個變量時知道這個變量緩存失效了,需要重新去內存拿。但是對於其他線程已經對這個變量完成操作只是還沒來得及寫回的情況無能爲力。
再深究一點,如果是非volatile修飾的變量,其執行步驟其實有5步:①內存讀取到緩存 → ②讀取緩存 → ③進行加1 → ④寫入緩存 → ⑤緩存寫入內存 。但volatile 隻影響了①→②(使得直接從內存讀取)、③(使得如果加1時判斷內存失效則重新從內存讀取)、④→⑤(使得直接寫回到內存),而無法影響③→④(已經執行過加1,只是等待寫回)。

保證一定的有序性

volatile關鍵字禁止指令重排序其實有兩層意思:

1)當程序執行到volatile變量的讀操作或者寫操作時,在其前面的操作的更改肯定全部已經進行,且結果已經對後面的操作可見;在其後面的操作肯定還沒有進行;

2)在進行指令優化時,不能將在對volatile變量訪問的語句放在其後面執行,也不能把volatile變量後面的語句放到其前面執行。

舉個例子:

x = 2;        //語句1
y = 0;        //語句2
flag = true;  //語句3
x = 4;        //語句4
y = -1;       //語句5

如果flag變量爲volatile變量,那麼指令在重排序的時候,不會將3放在1或者2之前執行,也不會將3放在4或者5之後執行。但對1、2兩者以及4、5兩者的執行順序不做保證。

使用場景

通常來說,使用volatile必須具備以下2個條件:

1)對變量的寫操作不依賴於當前值 

 2)該變量沒有包含在具有其他變量的不變式中  。也就是必須現有的條件已經能保證原子性的情況下,才能使用volatile關鍵字。

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