synchronized是一個重量級的鎖,volatile
一般被比喻成輕量級的synchronized
html
volatile
是一個變量修飾符,只能用來修飾變量。數據庫
volatile寫:當寫一個volatile變量時,JMM會把該線程對應的本地內存中的共享變量刷新到主內存。緩存
volatile讀:當讀一個volatile變量時,JMM會把該線程對應的本地內存置爲無效。線程接下來將從主內存中讀取共享變量。併發
1)JMM把內存屏障指令分爲下列四類:post
StoreLoad Barriers是一個「全能型」的屏障,它同時具備其餘三個屏障的效果。現代的多處理器大都支持該屏障(其餘類型的屏障不必定被全部處理器支持)。執行該屏障開銷會很昂貴,由於當前處理器一般要把寫緩衝區中的數據所有刷新到內存中(buffer fully flush)。優化
Store:數據對其餘處理器可見(即:刷新到內存)url
Load:讓緩存中的數據失效,從新從主內存加載數據 spa
2)JMM針對編譯器制定的volatile重排序規則表線程
是否能重排序 | 第二個操做 | ||
第一個操做 | 普通讀/寫 | volatile讀 | volatile寫 |
普通讀/寫 | NO | ||
volatile讀 | NO | NO | NO |
volatile寫 | NO | NO |
舉例來講,第三行最後一個單元格的意思是:在程序順序中,當第一個操做爲普通變量的讀或寫時,若是第二個操做爲volatile寫,則編譯器不能重排序這兩個操做。code
從上表咱們能夠看出:
JMM內存屏障插入策略(編譯器能夠根據具體狀況省略沒必要要的屏障):
StoreStore
Store2,在Store2及後續寫入操做執行前,保證Store1的寫入操做對其它處理器可見StoreLoad
Load2,在Load2及後續全部讀取操做執行前,保證Store1的寫入對全部處理器可見LoadLoad
Load2,在Load2及後續讀取操做要讀取的數據被訪問前,保證Load1要讀取的數據被讀取完畢LoadStore
Store2,在Store2及後續寫入操做被刷出前,保證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); } }
volatile的內存屏障插入策略很是保守,其實在實際中,只要不改變volatile寫-讀得內存語義,編譯器能夠根據具體狀況優化,省略沒必要要的屏障。參考:【死磕Java併發】—–Java內存模型之分析volatile