最近一直在看多執行緒的一些知識,看了一些書和一些部落格,收穫還是挺多的,最近看了《java併發程式設計的藝術》這本書感覺收穫很大也推薦給各位,同時也結合以前看的部落格就好好的總結一下自己所學的東西吧,有不足的地方歡迎各位指正,這篇文章主要是講volatile關鍵字的知識。
volatile的特性
- 可見性:volatile在多執行緒中能夠保證共享變數的“可見性”,簡單的說就是當一個執行緒修改了volatile變數的時候,java執行緒記憶體模型能夠確保所有的執行緒看到的這個變數的值是一致的。
- 防止指令重排序
java記憶體模型
- 在學習volatile的知識之前我們先來簡單瞭解下java記憶體模型(JMM)引用一張網上很經典的表示java記憶體模型的圖
- 大概解釋下這個圖的意思
- 在多個執行緒執行的時候每一個執行緒(Thread)都有一個屬於自己的記憶體空間
- 多個執行緒共同使用一個主存
- 每個執行緒在對資料進行修改之前都會先在主存裡面獲取相關資料,然後在自己的工作記憶體裡面對資料進行操作。
可見性
關鍵的地方就來了,因為每個執行緒然都是在自己的記憶體裡進行操作,然而每個執行緒的工作記憶體之間都是相互不可見的,所以對共享變數的修改並不會馬上被其他執行緒看到,所以就會造成多個執行緒操作同一個資料但是最後結果並不是我們期望的結果。當執行緒1去首先從主存中載入一個volatile變數到自己的工作內,然後對這個volatile變數進行寫操作,寫入操作結束之後,volatile變數的最新值會立馬重新整理到主記憶體,同時其他執行緒中的這個volatile變數會立馬失效,會被強迫從主記憶體中重新讀取volatile變數的最新值,這就是volatile變數的可見性實現的過程。同時這也可以看做是一個執行緒和其他執行緒通訊的一個過程。
防止指令重排序
簡單解釋下指令重排序,重排序指的是編譯器和處理器為了優化程式的效能會對指令序列進行重新排序的一種手段。在單執行緒的程式裡,指令的重排序會保證執行結果的正確性,但是在多執行緒中指令的重排序對程式的執行結果的正確性就得不到保障(指令重排序的一些規則各位可以去查閱一下,這裡不贅述)。volatile變數防止指令重排序請先看下面的記憶體屏障介紹。
記憶體屏障
JMM把記憶體屏障分為四類(摘自Java併發程式設計藝術)
屏障型別 | 指令示例 | 說明 |
---|---|---|
LoadLoad Barriers | Loadl; LoadLoad; Load2 | 確保Loadl資料的裝載先於Load2及所有後續裝載指令 |
StoreStore Barriers | Storel; StoreStore; Store2 | 確保Store1資料對其他處理器可見(重新整理到記憶體)先於Store2及所有後續儲存指令的儲存 |
LoadStore Barriers | Loadl; LoadStore; Store2 | 確保Loadl資料裝載先於Store2及所有後續的儲存指令重新整理到記憶體 |
StoreLoad Barriers | Storel; StoreLoad; Load2 | 確保Storel資料對其他處理器變得可見(指重新整理到記憶體)先於Load2及所有後續裝載指令的裝載。StoreLoad Barriers會使該屏障之前的所有記憶體訪問指令(儲存和裝載指令)完成之後,才執行該屏障之後的記憶體訪問指令 |
- volatile變數基於保守策略的JMM記憶體屏障插入策略
- 在每個volatile寫操作的前面插入一個StoreStore屏障。
- 在每個volatile寫操作的後面插入一個StoreLoad屏障。
- 在每個volatile讀操作的後面插入一個LoadLoad屏障。
- 在每個volatile讀操作的後面插入一個LoadStore屏障。
看完這個相信各位對volatile防止指令重排序就有一個比較清楚的認識了,解釋一下上面的四條策略,
- 在volatile變數的寫操作前面的其他寫操作會在volatile變數寫前面執行(提前重新整理到主存,對其他執行緒可見)
- volatile變數的寫會比後面的其他讀寫操作先進行
- volatile變數讀操作前面的讀操作會在volatile變數讀操作以前進行
- volatile變數讀操作後面的其他寫操作會在volatile變數讀操作以後進行
volatile的應用
很多部落格中基本上都說了volatile變數一般運用於不依附當前值的操作,比如自增,我的理解是這樣的,如果volatile變數進行依附當前操作的值的運算,那麼就會涉及到讀volatile變數和寫volatile變數這兩個操作,volatile變數的讀操作(從主記憶體讀取到執行緒的工作記憶體)和寫操作(將變數寫到主記憶體中去)時,這兩個組合起來的操作就是一個非原子性的操作,所以這種情況下使用volatile關鍵字就不合適,對於基本變數的一些非原子性操作(如自增)可以考慮使用java.util.concurrent.atomic包下的一些類,或者使用鎖來進行。volatile變數一般用作比如一個標誌變數這種單個讀寫的操作。
附上個人覺得volatile變數應用的一個講得比較好的部落格(www.ibm.com/developerwo…
同時各位也可以去看一下另外一篇講解volatile的博文,同樣比較棒(kwsir.cn/2017/10/12/…
寫在最後
鑑於本人水平有限,所以如果文章中有不對的地方,十分歡迎各位在評論留言指點,或者傳送到本人的qq郵箱549005114@qq.com通知一下本人。謝謝大家。