成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

從匯編看Volatile的內(nèi)存屏障

szysky / 3030人閱讀

摘要:為了實現(xiàn)的內(nèi)存語義,編譯器在生成字節(jié)碼時,會在指令序列中插入內(nèi)存屏障來禁止特定類型的處理器重排序。上述寫和讀的內(nèi)存屏障插入策略非常保守。

本講座地址https://segmentfault.com/l/15... 歡迎大家圍觀

Java的Volatile的特征是任何讀都能讀到最新值,本質(zhì)上是JVM通過內(nèi)存屏障來實現(xiàn)的,讓我們看看從字節(jié)碼以及匯編碼的角度,來看下是否真是如此?

一 Volatile與內(nèi)存屏障

本節(jié)內(nèi)容來自:http://www.infoq.com/cn/artic...

為了實現(xiàn)volatile內(nèi)存語義,JMM會分別限制重排序類型。下面是JMM針對編譯器制定的volatile重排序規(guī)則表:

舉例來說,第三行最后一個單元格的意思是:在程序順序中,當(dāng)?shù)谝粋€操作為普通變量的讀或?qū)憰r,如果第二個操作為volatile寫,則編譯器不能重排序這兩個操作。

從上表我們可以看出:

當(dāng)?shù)诙€操作是volatile寫時,不管第一個操作是什么,都不能重排序。這個規(guī)則確保volatile寫之前的操作不會被編譯器重排序到volatile寫之后。

當(dāng)?shù)谝粋€操作是volatile讀時,不管第二個操作是什么,都不能重排序。這個規(guī)則確保volatile讀之后的操作不會被編譯器重排序到volatile讀之前。

當(dāng)?shù)谝粋€操作是volatile寫,第二個操作是volatile讀時,不能重排序。

為了實現(xiàn)volatile的內(nèi)存語義,編譯器在生成字節(jié)碼時,會在指令序列中插入內(nèi)存屏障來禁止特定類型的處理器重排序。對于編譯器來說,發(fā)現(xiàn)一個最優(yōu)布置來最小化插入屏障的總數(shù)幾乎不可能,為此,JMM采取保守策略。下面是基于保守策略的JMM內(nèi)存屏障插入策略:

在每個volatile寫操作的前面插入一個StoreStore屏障。

在每個volatile寫操作的后面插入一個StoreLoad屏障。

在每個volatile讀操作的后面插入一個LoadLoad屏障。

在每個volatile讀操作的后面插入一個LoadStore屏障。

上述內(nèi)存屏障插入策略非常保守,但它可以保證在任意處理器平臺,任意的程序中都能得到正確的volatile內(nèi)存語義。

下面是保守策略下,volatile寫插入內(nèi)存屏障后生成的指令序列示意圖:

上圖中的StoreStore屏障可以保證在volatile寫之前,其前面的所有普通寫操作已經(jīng)對任意處理器可見了。這是因為StoreStore屏障將保障上面所有的普通寫在volatile寫之前刷新到主內(nèi)存。

這里比較有意思的是volatile寫后面的StoreLoad屏障。這個屏障的作用是避免volatile寫與后面可能有的volatile讀/寫操作重排序。因為編譯器常常無法準(zhǔn)確判斷在一個volatile寫的后面,是否需要插入一個StoreLoad屏障(比如,一個volatile寫之后方法立即return)。為了保證能正確實現(xiàn)volatile的內(nèi)存語義,JMM在這里采取了保守策略:在每個volatile寫的后面或在每個volatile讀的前面插入一個StoreLoad屏障。從整體執(zhí)行效率的角度考慮,JMM選擇了在每個volatile寫的后面插入一個StoreLoad屏障。因為volatile寫-讀內(nèi)存語義的常見使用模式是:一個寫線程寫volatile變量,多個讀線程讀同一個volatile變量。當(dāng)讀線程的數(shù)量大大超過寫線程時,選擇在volatile寫之后插入StoreLoad屏障將帶來可觀的執(zhí)行效率的提升。從這里我們可以看到JMM在實現(xiàn)上的一個特點(diǎn):首先確保正確性,然后再去追求執(zhí)行效率。

下面是在保守策略下,volatile讀插入內(nèi)存屏障后生成的指令序列示意圖:

上圖中的LoadLoad屏障用來禁止處理器把上面的volatile讀與下面的普通讀重排序。LoadStore屏障用來禁止處理器把上面的volatile讀與下面的普通寫重排序。

上述volatile寫和volatile讀的內(nèi)存屏障插入策略非常保守。在實際執(zhí)行時,只要不改變volatile寫-讀的內(nèi)存語義,編譯器可以根據(jù)具體情況省略不必要的屏障。下面我們通過具體的示例代碼來說明:

class VolatileBarrierExample {
    int a;
    volatile int v1 = 1;
    volatile int v2 = 2;

    void readAndWrite() {
        int i = v1;           //第一個volatile讀
        int j = v2;           // 第二個volatile讀
        a = i + j;            //普通寫
        v1 = i + 1;          // 第一個volatile寫
        v2 = j * 2;          //第二個 volatile寫
    }

    …                    //其他方法
}

針對readAndWrite()方法,編譯器在生成字節(jié)碼時可以做如下的優(yōu)化:

注意,最后的StoreLoad屏障不能省略。因為第二個volatile寫之后,方法立即return。此時編譯器可能無法準(zhǔn)確斷定后面是否會有volatile讀或?qū)懀瑸榱税踩鹨?,編譯器常常會在這里插入一個StoreLoad屏障。

上面的優(yōu)化是針對任意處理器平臺,由于不同的處理器有不同“松緊度”的處理器內(nèi)存模型,內(nèi)存屏障的插入還可以根據(jù)具體的處理器內(nèi)存模型繼續(xù)優(yōu)化。以x86處理器為例,上圖中除最后的StoreLoad屏障外,其它的屏障都會被省略。

前面保守策略下的volatile讀和寫,在 x86處理器平臺可以優(yōu)化成:

前文提到過,x86處理器僅會對寫-讀操作做重排序。X86不會對讀-讀,讀-寫和寫-寫操作做重排序,因此在x86處理器中會省略掉這三種操作類型對應(yīng)的內(nèi)存屏障。在x86中,JMM僅需在volatile寫后面插入一個StoreLoad屏障即可正確實現(xiàn)volatile寫-讀的內(nèi)存語義。這意味著在x86處理器中,volatile寫的開銷比volatile讀的開銷會大很多(因為執(zhí)行StoreLoad屏障開銷會比較大)。

二 Volatile的字節(jié)碼

為了搞清楚內(nèi)存屏障,我們扒開class字節(jié)碼看一下,用javap -v -p class文件名(不要.class 后綴)運(yùn)行

volatile int v1;
    descriptor: I
    flags: ACC_VOLATILE
   .....
void readAndWrite();
    descriptor: ()V
    flags:
    Code:
      stack=3, locals=3, args_size=1
         0: aload_0
         1: getfield      #52                 // Field v1:I
         4: istore_1
         5: aload_0
         6: getfield      #54                 // Field v2:I
         9: istore_2
        10: aload_0
        11: iload_1
        12: iload_2
        13: iadd
        14: putfield      #72                 // Field a:I
        17: aload_0
        18: iload_1
        19: iconst_1
        20: isub
        21: putfield      #52                 // Field v1:I
        24: aload_0
        25: iload_2
        26: iload_1
        27: imul
        28: putfield      #54                 // Field v2:I
        31: return

除了其變量定義的時候有一個Volatile外,之后的字節(jié)碼跟有無Volatile完全一樣,于是我們又扒了下匯編代碼

三 Volatile的匯編碼

為了看到匯編碼,要使用hsdis插件, 在mac系統(tǒng)下需要安裝一個hsdis-amd64.dylib的插件。在網(wǎng)上找了一個,地址在這里。
下載下來后,將其放置到你的jre lib目錄下即可。
mac系統(tǒng)上命令如下,

sudo mv ./hsdis-amd64.dylib  /Library/Java/JavaVirtualMachines/jdk1.8.0_31.jdk/Contents/Home/jre/lib

然后再運(yùn)行

java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp -XX:CompileCommand=dontinline,*VolatileBarrierExample.readAndWrite -XX:CompileCommand=compileonly,*VolatileBarrierExample.readAndWrite com.earnfish.VolatileBarrierExample > out.put

其中*VolatileBarrierExample.readAndWrite表示你運(yùn)行的類.函數(shù), com.earnfish.VolatileBarrierExample表示你的包名.類名,注意需要有main函數(shù)來運(yùn)行你所要執(zhí)行的函數(shù)。得出匯編碼如下

  0x000000011214bb49: mov    %rdi,%rax
  0x000000011214bb4c: dec    %eax
  0x000000011214bb4e: mov    %eax,0x10(%rsi)
  0x000000011214bb51: lock addl $0x0,(%rsp)     ;*putfield v1
                                                ; - com.earnfish.VolatileBarrierExample::readAndWrite@21 (line 35)

  0x000000011214bb56: imul   %edi,%ebx
  0x000000011214bb59: mov    %ebx,0x14(%rsi)
  0x000000011214bb5c: lock addl $0x0,(%rsp)     ;*putfield v2
                                                ; - com.earnfish.VolatileBarrierExample::readAndWrite@28 (line 36)

其對應(yīng)的Java代碼如下

 v1 = i - 1;          // 第一個volatile寫
 v2 = j * i;          // 第二個volatile寫

可見其本質(zhì)是通過一個lock指令來實現(xiàn)的。那么lock是什么意思呢?

查詢IA32手冊,它的作用是使得本CPU的Cache寫入了內(nèi)存,該寫入動作也會引起別的CPU invalidate其Cache。所以通過這樣一個空操作,可讓前面volatile變量的修改對其他CPU立即可見。

所以,它的作用是

鎖住主存

任何讀必須在寫完成之后再執(zhí)行

使其它線程這個值的棧緩存失效

類似于前面是storestore,后面是storeload

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/69025.html

相關(guān)文章

  • volatile,可見性,有序性

    摘要:內(nèi)存語義的的實現(xiàn)可見性的實現(xiàn)基于的讀取,寫入兩個操作的內(nèi)存語義。首先,對中內(nèi)存屏障的介紹內(nèi)存屏障用于控制特定條件下的重排序和內(nèi)存可見性問題。在大多數(shù)處理器的實現(xiàn)中,這個屏障是個萬能屏障,兼具其它三種內(nèi)存屏障的功能。 volatile,可見性,有序性 volatile的特性 可見性:對一個volatile變量的讀,總能獲取其他任意線程對該變量最后的寫入。 有序性:JMM會限制volat...

    caige 評論0 收藏0
  • (七)Volatile作用及原理

    摘要:文章簡介分析的作用以及底層實現(xiàn)原理,這也是大公司喜歡問的問題內(nèi)容導(dǎo)航的作用什么是可見性源碼分析的作用在多線程中,和都起到非常重要的作用,是通過加鎖來實現(xiàn)線程的安全性。而的主要作用是在多處理器開發(fā)中保證共享變量對于多線程的可見性。 文章簡介 分析volatile的作用以及底層實現(xiàn)原理,這也是大公司喜歡問的問題 內(nèi)容導(dǎo)航 volatile的作用 什么是可見性 volatile源碼分析 ...

    marek 評論0 收藏0
  • volatile詳解

    摘要:內(nèi)存模型基本概念計算機(jī)在執(zhí)行程序時,每條指令都是在中執(zhí)行的,而執(zhí)行指令過程中,勢必涉及到數(shù)據(jù)的讀取和寫入。有序性即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。 內(nèi)存模型基本概念 計算機(jī)在執(zhí)行程序時,每條指令都是在CPU中執(zhí)行的,而執(zhí)行指令過程中,勢必涉及到數(shù)據(jù)的讀取和寫入。由于程序運(yùn)行過程中的臨時數(shù)據(jù)是存放在主存(物理內(nèi)存)當(dāng)中的,這時就存在一個問題,由于CPU執(zhí)行速度很快,而從內(nèi)存讀取數(shù)據(jù)...

    aikin 評論0 收藏0
  • Java并發(fā)編程,3分分鐘深入分析volatile實現(xiàn)原理

    摘要:一言以蔽之,被修飾的變量能夠保證每個線程能夠獲取該變量的最新值,從而避免出現(xiàn)數(shù)據(jù)臟讀的現(xiàn)象。為了實現(xiàn)內(nèi)存語義時,編譯器在生成字節(jié)碼時,會在指令序列中插入內(nèi)存屏障來禁止特定類型的處理器重排序。volatile原理volatile簡介Java內(nèi)存模型告訴我們,各個線程會將共享變量從主內(nèi)存中拷貝到工作內(nèi)存,然后執(zhí)行引擎會基于工作內(nèi)存中的數(shù)據(jù)進(jìn)行操作處理。 線程在工作內(nèi)存進(jìn)行操作后何時會寫到主內(nèi)存中...

    番茄西紅柿 評論0 收藏0
  • Java并發(fā)編程,3分分鐘深入分析volatile實現(xiàn)原理

    摘要:一言以蔽之,被修飾的變量能夠保證每個線程能夠獲取該變量的最新值,從而避免出現(xiàn)數(shù)據(jù)臟讀的現(xiàn)象。為了實現(xiàn)內(nèi)存語義時,編譯器在生成字節(jié)碼時,會在指令序列中插入內(nèi)存屏障來禁止特定類型的處理器重排序。volatile原理volatile簡介Java內(nèi)存模型告訴我們,各個線程會將共享變量從主內(nèi)存中拷貝到工作內(nèi)存,然后執(zhí)行引擎會基于工作內(nèi)存中的數(shù)據(jù)進(jìn)行操作處理。 線程在工作內(nèi)存進(jìn)行操作后何時會寫到主內(nèi)存中...

    番茄西紅柿 評論0 收藏0

發(fā)表評論

0條評論

szysky

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<