執行緒中的關鍵字volatile的介紹
阿新 • • 發佈:2018-11-10
volatile關鍵字修飾
變數:可以保證變數的可見性和防止程式指令重排序
一:保證執行緒間的可見性
1)保證了不同執行緒對這個變數進行操作時的可見性,即一個執行緒修改了某個變數的值,這新值對其他執行緒來說是立即可見的。
2)禁止進行指令重排序
先看一段程式碼,假如執行緒1先執行,執行緒2後執行:
//執行緒1
boolean
stop =
false
;
while
(!stop){
doSomething();
}
//執行緒2
stop =
true
;
這段程式碼是很典型的一段程式碼,很多人在中斷執行緒時可能都會採用這種標記辦法。但是事實上,這段程式碼會完全執行正確麼?即一定會將執行緒中斷麼?不一定,也許在大多數時候,這個程式碼能夠把執行緒中斷,但是也有可能會導致無法中斷執行緒(雖然這個可能性很小,但是隻要一旦發生這種情況就會造成死迴圈了)。
下面解釋一下這段程式碼為何有可能導致無法中斷執行緒。在前面已經解釋過,每個執行緒在執行過程中都有自己的工作記憶體,那麼執行緒1在執行的時候,會將stop變數的值拷貝一份放在自己的工作記憶體當中。
那麼當執行緒2更改了stop變數的值之後,但是還沒來得及寫入主存當中,執行緒2轉去做其他事情了,那麼執行緒1由於不知道執行緒2對stop變數的更改,因此還會一直迴圈下去。
但是用volatile修飾之後就變得不一樣了:
第一:使用volatile關鍵字會強制將修改的值立即寫入主存;
第二:使用volatile關鍵字的話,當執行緒2進行修改時,會導致執行緒1的工作記憶體中快取變數stop的快取行無效(反映到硬體層的話,就是CPU的L1或者L2快取中對應的快取行無效);
第三:由於執行緒1的工作記憶體中快取變數stop的快取行無效,所以執行緒1再次讀取變數stop的值時會去主存讀取。
那麼線上程2修改stop值時(當然這裡包括2個操作,修改執行緒2工作記憶體中的值,然後將修改後的值寫入記憶體),會使得執行緒1的工作記憶體中快取變數stop的快取行無效,然後執行緒1讀取時,發現自己的快取行無效,它會等待快取行對應的主存地址被更新之後,然後去對應的主存讀取最新的值。
那麼執行緒1讀取到的就是最新的正確的值。
2.volatile不能確保原子性
volatile的應用場景
synchronized關鍵字是防止多個執行緒同時執行一段程式碼,那麼就會很影響程式執行效率,而volatile關鍵字在某些情況下效能要優於synchronized,但是要注意volatile關鍵字是無法替代synchronized關鍵字的,因為volatile關鍵字無法保證操作的原子性。通常來說,使用volatile必須具備以下2個條件:
1)對變數的寫操作不依賴於當前值
2)該變數沒有包含在具有其他變數的不變式中
想要理解volatile為什麼能確保可見性,就要先理解Java中的記憶體模型是什麼樣的。
Java記憶體模型規定了所有的變數都儲存在主記憶體中。每條執行緒中還有自己的工作記憶體,執行緒的工作記憶體中儲存了被該執行緒所使用到的變數(這些變數是從主記憶體中拷貝而來)。執行緒對變數的所有操作(讀取,賦值)都必須在工作記憶體中進行。不同執行緒之間也無法直接訪問對方工作記憶體中的變數,執行緒間變數值的傳遞均需要通過主記憶體來完成。
基於此種記憶體模型,便產生了多執行緒程式設計中的資料“髒讀”等問題。