synchronized 是一個(gè)重量級的鎖, volatile 通常被比喻成輕量級的 synchronized
volatile 是一個(gè)變量修飾符,只能用來修飾變量。
volatile寫:當(dāng)寫一個(gè)volatile變量時(shí),JMM會把該線程對應(yīng)的本地內(nèi)存中的共享變量刷新到主內(nèi)存。
volatile讀:當(dāng)讀一個(gè)volatile變量時(shí),JMM會把該線程對應(yīng)的本地內(nèi)存置為無效。線程接下來將從主內(nèi)存中讀取共享變量。
volatile實(shí)現(xiàn)原理
1)JMM把內(nèi)存屏障指令分為下列四類:
StoreLoad Barriers是一個(gè)“全能型”的屏障,它同時(shí)具有其他三個(gè)屏障的效果?,F(xiàn)代的多處理器大都支持該屏障(其他類型的屏障不一定被所有處理器支持)。執(zhí)行該屏障開銷會很昂貴,因?yàn)楫?dāng)前處理器通常要把寫緩沖區(qū)中的數(shù)據(jù)全部刷新到內(nèi)存中(buffer fully flush)。
Store:數(shù)據(jù)對其他處理器可見(即:刷新到內(nèi)存)
Load:讓緩存中的數(shù)據(jù)失效,重新從主內(nèi)存加載數(shù)據(jù)
2)JMM針對編譯器制定的volatile重排序規(guī)則表
是否能重排序 第二個(gè)操作
第一個(gè)操作 普通讀/寫 volatile讀 volatile寫
普通讀/寫 NO
volatile讀 NO NO NO
volatile寫 NO NO
舉例來說,第三行最后一個(gè)單元格的意思是:在程序順序中,當(dāng)?shù)谝粋€(gè)操作為普通變量的讀或?qū)憰r(shí),如果第二個(gè)操作為volatile寫,則編譯器不能重排序這兩個(gè)操作。
從上表我們可以看出:
當(dāng)?shù)诙€(gè)操作是volatile寫時(shí),不管第一個(gè)操作是什么,都不能重排序。這個(gè)規(guī)則確保volatile寫之前的操作不會被編譯器重排序到volatile寫之后。
當(dāng)?shù)谝粋€(gè)操作是volatile讀時(shí),不管第二個(gè)操作是什么,都不能重排序。這個(gè)規(guī)則確保volatile讀之后的操作不會被編譯器重排序到volatile讀之前。
當(dāng)?shù)谝粋€(gè)操作是volatile寫,第二個(gè)操作是volatile讀時(shí),不能重排序。
JMM內(nèi)存屏障插入策略(編譯器可以根據(jù)具體情況省略不必要的屏障):
在每個(gè)volatile寫操作的前面插入一個(gè)StoreStore屏障。
對于這樣的語句Store1; StoreStore ; Store2,在Store2及后續(xù)寫入操作執(zhí)行前,保證Store1的寫入操作對其它處理器可見。
在每個(gè)volatile寫操作的后面插入一個(gè)StoreLoad屏障。
對于這樣的語句Store1; StoreLoad ; Load2,在Load2及后續(xù)所有讀取操作執(zhí)行前,保證Store1的寫入對所有處理器可見。
在每個(gè)volatile讀操作的后面插入一個(gè)LoadLoad屏障。
對于這樣的語句Load1; LoadLoad ; Load2,在Load2及后續(xù)讀取操作要讀取的數(shù)據(jù)被訪問前,保證Load1要讀取的數(shù)據(jù)被讀取完畢。
在每個(gè)volatile讀操作的后面插入一個(gè)LoadStore屏障。
對于這樣的語句Load1; LoadStore ; Store2,在Store2及后續(xù)寫入操作被刷出前,保證Load1要讀取的數(shù)據(jù)被讀取完畢。
volatile保證可見性
volatile修飾的變量寫之后將本地內(nèi)存刷新到主內(nèi)存,保證了可見性
volatile保證有序性
volatile變量讀寫前后插入內(nèi)存屏障以避免重排序,保證了有序性
volatile不保證原子性
volatile不是鎖,與原子性無關(guān)
要我說,由于CPU按照時(shí)間片來進(jìn)行線程調(diào)度的,只要是包含多個(gè)步驟的操作的執(zhí)行,天然就是無法保證原子性的。因?yàn)檫@種線程執(zhí)行,又不像數(shù)據(jù)庫一樣可以回滾。如果一個(gè)線程要執(zhí)行的步驟有5步,執(zhí)行完3步就失去了CPU了,失去后就可能再也不會被調(diào)度,這怎么可能保證原子性呢。
為什么 synchronized 可以保證原子性 ,因?yàn)楸?synchronized 修飾的代碼片段,在進(jìn)入之前加了鎖,只要他沒執(zhí)行完,其他線程是無法獲得鎖執(zhí)行這段代碼片段的,就可以保證他內(nèi)部的代碼可以全部被執(zhí)行。進(jìn)而保證原子性。(摘自http://www.hollischuang.com/archives/2673)
volatile不保證原子性的例子:
/**
* 創(chuàng)建10個(gè)線程,然后分別執(zhí)行1000次i++操作。目的是程序輸出結(jié)果10000
* 但是,多次執(zhí)行的結(jié)果都小于10000。這其實(shí)就是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)
// 保證前面的線程都執(zhí)行完
Thread.yield();
System.out.println(test.inc);
}
}