Java并發(fā):volatile的實(shí)現(xiàn)原理

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);

}

}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容