Java並發(四):volatile的實現原理
synchronized是一個重量級的鎖,volatile
通常被比喻成輕量級的synchronized
volatile
是一個變量修飾符,只能用來修飾變量。
volatile寫:當寫一個volatile變量時,JMM會把該線程對應的本地內存中的共享變量刷新到主內存。
volatile讀:當讀一個volatile變量時,JMM會把該線程對應的本地內存置為無效。線程接下來將從主內存中讀取共享變量。
volatile實現原理
1)JMM把內存屏障指令分為下列四類:
StoreLoad Barriers是一個“全能型”的屏障,它同時具有其他三個屏障的效果。現代的多處理器大都支持該屏障(其他類型的屏障不一定被所有處理器支持)。執行該屏障開銷會很昂貴,因為當前處理器通常要把寫緩沖區中的數據全部刷新到內存中(buffer fully flush)。
Store:數據對其他處理器可見(即:刷新到內存)
Load:讓緩存中的數據失效,重新從主內存加載數據
2)JMM針對編譯器制定的volatile重排序規則表
是否能重排序 | 第二個操作 | ||
第一個操作 | 普通讀/寫 | volatile讀 | volatile寫 |
普通讀/寫 | NO | ||
volatile讀 | NO | NO | NO |
volatile寫 | NO | NO |
舉例來說,第三行最後一個單元格的意思是:在程序順序中,當第一個操作為普通變量的讀或寫時,如果第二個操作為volatile寫,則編譯器不能重排序這兩個操作。
從上表我們可以看出:
- 當第二個操作是volatile寫時,不管第一個操作是什麽,都不能重排序。這個規則確保volatile寫之前的操作不會被編譯器重排序到volatile寫之後。
- 當第一個操作是volatile讀時,不管第二個操作是什麽,都不能重排序。這個規則確保volatile讀之後的操作不會被編譯器重排序到volatile讀之前。
- 當第一個操作是volatile寫,第二個操作是volatile讀時,不能重排序。
JMM內存屏障插入策略(編譯器可以根據具體情況省略不必要的屏障):
- 在每個volatile寫操作的前面插入一個StoreStore屏障。
- 對於這樣的語句Store1;
StoreStore
; Store2,在Store2及後續寫入操作執行前,保證Store1的寫入操作對其它處理器可見。
- 對於這樣的語句Store1;
- 在每個volatile寫操作的後面插入一個StoreLoad屏障。
- 對於這樣的語句Store1;
StoreLoad
- 對於這樣的語句Store1;
- 在每個volatile讀操作的後面插入一個LoadLoad屏障。
- 對於這樣的語句Load1;
LoadLoad
; Load2,在Load2及後續讀取操作要讀取的數據被訪問前,保證Load1要讀取的數據被讀取完畢。
- 對於這樣的語句Load1;
- 在每個volatile讀操作的後面插入一個LoadStore屏障。
- 對於這樣的語句Load1;
LoadStore
; Store2,在Store2及後續寫入操作被刷出前,保證Load1要讀取的數據被讀取完畢。
- 對於這樣的語句Load1;
volatile保證可見性
volatile修飾的變量寫之後將本地內存刷新到主內存,保證了可見性
volatile保證有序性
volatile變量讀寫前後插入內存屏障以避免重排序,保證了有序性
volatile不保證原子性
volatile不是鎖,與原子性無關
要我說,由於CPU按照時間片來進行線程調度的,只要是包含多個步驟的操作的執行,天然就是無法保證原子性的。因為這種線程執行,又不像數據庫一樣可以回滾。如果一個線程要執行的步驟有5步,執行完3步就失去了CPU了,失去後就可能再也不會被調度,這怎麽可能保證原子性呢。
為什麽synchronized
可以保證原子性 ,因為被synchronized
修飾的代碼片段,在進入之前加了鎖,只要他沒執行完,其他線程是無法獲得鎖執行這段代碼片段的,就可以保證他內部的代碼可以全部被執行。進而保證原子性。(摘自http://www.hollischuang.com/archives/2673)
volatile不保證原子性的例子:
/** * 創建10個線程,然後分別執行1000次i++操作。目的是程序輸出結果10000 * 但是,多次執行的結果都小於10000。這其實就是volatile無法滿足原子性的原因。 */ public class Test { public volatile int inc = 0; public void increase() { inc++; } public static void main(String[] args) { final Test test = new Test(); for (int i = 0; i < 10; i++) { new Thread() { public void run() { for (int j = 0; j < 1000; j++) test.increase(); }; }.start(); } while (Thread.activeCount() > 1) // 保證前面的線程都執行完 Thread.yield(); System.out.println(test.inc); } }
參考資料
深入理解Java中的volatile關鍵字
Java並發(一):Java內存模型幹貨總結
【死磕Java並發】—–深入分析volatile的實現原理
再有人問你volatile是什麽,把這篇文章也發給他。
Java並發(四):volatile的實現原理