日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

JVM内存模型、指令重排、内存屏障概念解析

發(fā)布時(shí)間:2025/3/21 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JVM内存模型、指令重排、内存屏障概念解析 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

在高并發(fā)模型中,無是面對物理機(jī)SMP系統(tǒng)模型,還是面對像JVM的虛擬機(jī)多線程并發(fā)內(nèi)存模型,指令重排(編譯器、運(yùn)行時(shí))和內(nèi)存屏障都是非常重要的概念,因此,搞清楚這些概念和原理很重要。否則,你很難搞清楚哪些操作是在并發(fā)先絕對安全的?哪些是相對安全的?哪些并發(fā)同步手段性能最低?valotile的二層語義分別是什么?等等。

? ? ? 本來打算自己寫一篇有關(guān)JVM內(nèi)存模型的博文,后來整理資料的時(shí)候偶然發(fā)現(xiàn)一篇很好的相關(guān)文章(出自美團(tuán)點(diǎn)評團(tuán)隊(duì)),個(gè)人感覺這篇文章寫得比較全面,最起碼概念層的東西講清楚了,遂轉(zhuǎn)載給大家。原文地址:http://tech.meituan.com/java-memory-reordering.html

一、什么是重排序

請先看這樣一段代碼

1 public class PossibleReordering {2 static int x = 0, y = 0;3 static int a = 0, b = 0;4 5 public static void main(String[] args) throws InterruptedException {6 Thread one = new Thread(new Runnable() {7 public void run() {8 a = 1;9 x = b; 10 } 11 }); 12 13 Thread other = new Thread(new Runnable() { 14 public void run() { 15 b = 1; 16 y = a; 17 } 18 }); 19 one.start();other.start(); 20 one.join();other.join(); 21 System.out.println(“(” + x + “,” + y + “)”); 22 }

? ? ? 很容易想到這段代碼的運(yùn)行結(jié)果可能為(1,0)、(0,1)或(1,1),因?yàn)榫€程one可以在線程two開始之前就執(zhí)行完了,也有可能反之,甚至有可能二者的指令是同時(shí)或交替執(zhí)行的。

? ? ? 然而,這段代碼的執(zhí)行結(jié)果也可能是(0,0). 因?yàn)?#xff0c;在實(shí)際運(yùn)行時(shí),代碼指令可能并不是嚴(yán)格按照代碼語句順序執(zhí)行的。得到(0,0)結(jié)果的語句執(zhí)行過程,如下圖所示。值得注意的是,a=1和x=b這兩個(gè)語句的賦值操作的順序被顛倒了,或者說,發(fā)生了指令“重排序”(reordering)。(事實(shí)上,輸出了這一結(jié)果,并不代表一定發(fā)生了指令重排序,內(nèi)存可見性問題也會(huì)導(dǎo)致這樣的輸出,詳見后文)

? ?

? ? ?對重排序現(xiàn)象不太了解的開發(fā)者可能會(huì)對這種現(xiàn)象感到吃驚,但是,筆者開發(fā)環(huán)境下做的一個(gè)小實(shí)驗(yàn)證實(shí)了這一結(jié)果。

? ? ?實(shí)驗(yàn)代碼是構(gòu)造一個(gè)循環(huán),反復(fù)執(zhí)行上面的實(shí)例代碼,直到出現(xiàn)a=0且b=0的輸出為止。實(shí)驗(yàn)結(jié)果說明,循環(huán)執(zhí)行到第13830次時(shí)輸出了(0,0)。

? ? ?大多數(shù)現(xiàn)代微處理器都會(huì)采用將指令亂序執(zhí)行(out-of-order execution,簡稱OoOE或OOE)的方法,在條件允許的情況下,直接運(yùn)行當(dāng)前有能力立即執(zhí)行的后續(xù)指令,避開獲取下一條指令所需數(shù)據(jù)時(shí)造成的等待3。通過亂序執(zhí)行的技術(shù),處理器可以大大提高執(zhí)行效率。
? ? ?除了處理器,常見的Java運(yùn)行時(shí)環(huán)境的JIT編譯器也會(huì)做指令重排序操作,即生成的機(jī)器指令與字節(jié)碼指令順序不一致。

二、as-if-serial語義

? ? ?As-if-serial語義的意思是,所有的動(dòng)作(Action)都可以為了優(yōu)化而被重排序,但是必須保證它們重排序后的結(jié)果和程序代碼本身的應(yīng)有結(jié)果是一致的。Java編譯器、運(yùn)行時(shí)和處理器都會(huì)保證單線程下的as-if-serial語義。
? ? ?比如,為了保證這一語義,重排序不會(huì)發(fā)生在有數(shù)據(jù)依賴的操作之中。

int a = 1; int b = 2; int c = a + b;

? ? ? 將上面的代碼編譯成Java字節(jié)碼或生成機(jī)器指令,可視為展開成了以下幾步動(dòng)作(實(shí)際可能會(huì)省略或添加某些步驟)。

  • 對a賦值1
  • 對b賦值2
  • 取a的值
  • 取b的值
  • 將取到兩個(gè)值相加后存入c
  • ? ? ? 在上面5個(gè)動(dòng)作中,動(dòng)作1可能會(huì)和動(dòng)作2、4重排序,動(dòng)作2可能會(huì)和動(dòng)作1、3重排序,動(dòng)作3可能會(huì)和動(dòng)作2、4重排序,動(dòng)作4可能會(huì)和1、3重排序。但動(dòng)作1和動(dòng)作3、5不能重排序。動(dòng)作2和動(dòng)作4、5不能重排序。因?yàn)樗鼈冎g存在數(shù)據(jù)依賴關(guān)系,一旦重排,as-if-serial語義便無法保證。

    ? ? ? 為保證as-if-serial語義,Java異常處理機(jī)制也會(huì)為重排序做一些特殊處理。例如在下面的代碼中,y = 0 / 0可能會(huì)被重排序在x = 2之前執(zhí)行,為了保證最終不致于輸出x = 1的錯(cuò)誤結(jié)果,JIT在重排序時(shí)會(huì)在catch語句中插入錯(cuò)誤代償代碼,將x賦值為2,將程序恢復(fù)到發(fā)生異常時(shí)應(yīng)有的狀態(tài)。這種做法的確將異常捕捉的邏輯變得復(fù)雜了,但是JIT的優(yōu)化的原則是,盡力優(yōu)化正常運(yùn)行下的代碼邏輯,哪怕以catch塊邏輯變得復(fù)雜為代價(jià),畢竟,進(jìn)入catch塊內(nèi)是一種“異常”情況的表現(xiàn)。

    1 public class Reordering {2 public static void main(String[] args) {3 int x, y;4 x = 1;5 try {6 x = 2;7 y = 0 / 0; 8 } catch (Exception e) {9 } finally { 10 System.out.println("x = " + x); 11 } 12 } 13 }

    三、內(nèi)存訪問重排序與內(nèi)存可見性

    ? ? ? 計(jì)算機(jī)系統(tǒng)中,為了盡可能地避免處理器訪問主內(nèi)存的時(shí)間開銷,處理器大多會(huì)利用緩存(cache)以提高性能。其模型如下圖所示。

    ? ? ?在這種模型下會(huì)存在一個(gè)現(xiàn)象,即緩存中的數(shù)據(jù)與主內(nèi)存的數(shù)據(jù)并不是實(shí)時(shí)同步的,各CPU(或CPU核心)間緩存的數(shù)據(jù)也不是實(shí)時(shí)同步的。這導(dǎo)致在同一個(gè)時(shí)間點(diǎn),各CPU所看到同一內(nèi)存地址的數(shù)據(jù)的值可能是不一致的。從程序的視角來看,就是在同一個(gè)時(shí)間點(diǎn),各個(gè)線程所看到的共享變量的值可能是不一致的。
    ? ? ?有的觀點(diǎn)會(huì)將這種現(xiàn)象也視為重排序的一種,命名為“內(nèi)存系統(tǒng)重排序”。因?yàn)檫@種內(nèi)存可見性問題造成的結(jié)果就好像是內(nèi)存訪問指令發(fā)生了重排序一樣。
    ? ? ?這種內(nèi)存可見性問題也會(huì)導(dǎo)致章節(jié)一中示例代碼即便在沒有發(fā)生指令重排序的情況下的執(zhí)行結(jié)果也還是(0, 0)。

    四、內(nèi)存訪問重排序與Java內(nèi)存模型

    ? ? ? Java的目標(biāo)是成為一門平臺無關(guān)性的語言,即Write once, run anywhere. 但是不同硬件環(huán)境下指令重排序的規(guī)則不盡相同。例如,x86下運(yùn)行正常的Java程序在IA64下就可能得到非預(yù)期的運(yùn)行結(jié)果。為此,JSR-1337制定了Java內(nèi)存模型(Java Memory Model, JMM),旨在提供一個(gè)統(tǒng)一的可參考的規(guī)范,屏蔽平臺差異性。從Java 5開始,Java內(nèi)存模型成為Java語言規(guī)范的一部分。
    ? ? ?根據(jù)Java內(nèi)存模型中的規(guī)定,可以總結(jié)出以下幾條happens-before規(guī)則。Happens-before的前后兩個(gè)操作不會(huì)被重排序且后者對前者的內(nèi)存可見。

    • 程序次序法則:線程中的每個(gè)動(dòng)作A都happens-before于該線程中的每一個(gè)動(dòng)作B,其中,在程序中,所有的動(dòng)作B都能出現(xiàn)在A之后。
    • 監(jiān)視器鎖法則:對一個(gè)監(jiān)視器鎖的解鎖 happens-before于每一個(gè)后續(xù)對同一監(jiān)視器鎖的加鎖。
    • volatile變量法則:對volatile域的寫入操作happens-before于每一個(gè)后續(xù)對同一個(gè)域的讀寫操作。
    • 線程啟動(dòng)法則:在一個(gè)線程里,對Thread.start的調(diào)用會(huì)happens-before于每個(gè)啟動(dòng)線程的動(dòng)作。
    • 線程終結(jié)法則:線程中的任何動(dòng)作都happens-before于其他線程檢測到這個(gè)線程已經(jīng)終結(jié)、或者從Thread.join調(diào)用中成功返回,或Thread.isAlive返回false。
    • 中斷法則:一個(gè)線程調(diào)用另一個(gè)線程的interrupt happens-before于被中斷的線程發(fā)現(xiàn)中斷。
    • 終結(jié)法則:一個(gè)對象的構(gòu)造函數(shù)的結(jié)束happens-before于這個(gè)對象finalizer的開始。
    • 傳遞性:如果A happens-before于B,且B happens-before于C,則A happens-before于C

    ? ? ? Happens-before關(guān)系只是對Java內(nèi)存模型的一種近似性的描述,它并不夠嚴(yán)謹(jǐn),但便于日常程序開發(fā)參考使用,關(guān)于更嚴(yán)謹(jǐn)?shù)腏ava內(nèi)存模型的定義和描述,請閱讀JSR-133原文或Java語言規(guī)范章節(jié)17.4。

    ? ? ? 除此之外,Java內(nèi)存模型對volatile和final的語義做了擴(kuò)展。對volatile語義的擴(kuò)展保證了volatile變量在一些情況下不會(huì)重排序,volatile的64位變量double和long的讀取和賦值操作都是原子的。對final語義的擴(kuò)展保證一個(gè)對象的構(gòu)建方法結(jié)束前,所有final成員變量都必須完成初始化(的前提是沒有this引用溢出)。

    ? ? ? Java內(nèi)存模型關(guān)于重排序的規(guī)定,總結(jié)后如下表所示。

    ? ? ? 表中“第二項(xiàng)操作”的含義是指,第一項(xiàng)操作之后的所有指定操作。如,普通讀不能與其之后的所有volatile寫重排序。另外,JMM也規(guī)定了上述volatile和同步塊的規(guī)則盡適用于存在多線程訪問的情景。例如,若編譯器(這里的編譯器也包括JIT,下同)證明了一個(gè)volatile變量只能被單線程訪問,那么就可能會(huì)把它做為普通變量來處理。
    ? ? ? 留白的單元格代表允許在不違反Java基本語義的情況下重排序。例如,編譯器不會(huì)對對同一內(nèi)存地址的讀和寫操作重排序,但是允許對不同地址的讀和寫操作重排序。

    ? ? ? 除此之外,為了保證final的新增語義。JSR-133對于final變量的重排序也做了限制。

      • 構(gòu)建方法內(nèi)部的final成員變量的存儲(chǔ),并且,假如final成員變量本身是一個(gè)引用的話,這個(gè)final成員變量可以引用到的一切存儲(chǔ)操作,都不能與構(gòu)建方法外的將當(dāng)期構(gòu)建對象賦值于多線程共享變量的存儲(chǔ)操作重排序。例如對于如下語句
        x.finalField = v; ... ;構(gòu)建方法邊界sharedRef = x;
        v.afield = 1; x.finalField = v; ... ; 構(gòu)建方法邊界sharedRef = x;
        這兩條語句中,構(gòu)建方法邊界前后的指令都不能重排序。
      • 初始讀取共享對象與初始讀取該共享對象的final成員變量之間不能重排序。例如對于如下語句
        x = sharedRef; ... ; i = x.finalField;
        前后兩句語句之間不會(huì)發(fā)生重排序。由于這兩句語句有數(shù)據(jù)依賴關(guān)系,編譯器本身就不會(huì)對它們重排序,但確實(shí)有一些處理器會(huì)對這種情況重排序,因此特別制定了這一規(guī)則。

    五、內(nèi)存屏障

    ? ? ? 內(nèi)存屏障(Memory Barrier,或有時(shí)叫做內(nèi)存柵欄,Memory Fence)是一種CPU指令,用于控制特定條件下的重排序和內(nèi)存可見性問題。Java編譯器也會(huì)根據(jù)內(nèi)存屏障的規(guī)則禁止重排序。
    ? ? ? 內(nèi)存屏障可以被分為以下幾種類型
    LoadLoad屏障:對于這樣的語句Load1; LoadLoad; Load2,在Load2及后續(xù)讀取操作要讀取的數(shù)據(jù)被訪問前,保證Load1要讀取的數(shù)據(jù)被讀取完畢。
    StoreStore屏障:對于這樣的語句Store1; StoreStore; Store2,在Store2及后續(xù)寫入操作執(zhí)行前,保證Store1的寫入操作對其它處理器可見。
    LoadStore屏障:對于這樣的語句Load1; LoadStore; Store2,在Store2及后續(xù)寫入操作被刷出前,保證Load1要讀取的數(shù)據(jù)被讀取完畢。
    StoreLoad屏障:對于這樣的語句Store1; StoreLoad; Load2,在Load2及后續(xù)所有讀取操作執(zhí)行前,保證Store1的寫入對所有處理器可見。它的開銷是四種屏障中最大的。 ? ? ? ?在大多數(shù)處理器的實(shí)現(xiàn)中,這個(gè)屏障是個(gè)萬能屏障,兼具其它三種內(nèi)存屏障的功能。

    ? ? ? ?有的處理器的重排序規(guī)則較嚴(yán),無需內(nèi)存屏障也能很好的工作,Java編譯器會(huì)在這種情況下不放置內(nèi)存屏障。
    ? ? ? ?為了實(shí)現(xiàn)上一章中討論的JSR-133的規(guī)定,Java編譯器會(huì)這樣使用內(nèi)存屏障。

    ? ? ?為了保證final字段的特殊語義,也會(huì)在下面的語句加入內(nèi)存屏障。
    ? ? ?x.finalField = v; StoreStore; sharedRef = x;

    六、Intel 64/IA-32架構(gòu)下的內(nèi)存訪問重排序

    ? ? ?Intel 64和IA-32是我們較常用的硬件環(huán)境,相對于其它處理器而言,它們擁有一種較嚴(yán)格的重排序規(guī)則。Pentium 4以后的Intel 64或IA-32處理的重排序規(guī)則如下。9

    在單CPU系統(tǒng)中

    • 讀操作不與其它讀操作重排序。
    • 寫操作不與其之前的寫操作重排序。
    • 寫內(nèi)存操作不與其它寫操作重排序,但有以下幾種例外
    • CLFLUSH的寫操作
    • 帶有non-temporal move指令(MOVNTI, MOVNTQ, MOVNTDQ, MOVNTPS, and MOVNTPD)的streaming寫入。
    • 字符串操作
    • 讀操作可能會(huì)與其之前的寫不同位置的寫操作重排序,但不與其之前的寫相同位置的寫操作重排序。
    • 讀和寫操作不與I/O指令,帶鎖的指令或序列化指令重排序。
    • 讀操作不能重排序到LFENCE和MFENCE之前。
    • 寫操作不能重排序到LFENCE、SFENCE和MFENCE之前。
    • LFENCE不能重排序到讀操作之前。
    • SFENCE不能重排序到寫之前。
    • MFENCE不能重排序到讀或?qū)懖僮髦啊?/li>

    在多處理器系統(tǒng)中

    • 各自處理器內(nèi)部遵循單處理器的重排序規(guī)則。
    • 單處理器的寫操作對所有處理器可見是同時(shí)的。
    • 各自處理器的寫操作不會(huì)重排序。
    • 內(nèi)存重排序遵守因果性(causality)(內(nèi)存重排序遵守傳遞可見性)。
    • 任何寫操作對于執(zhí)行這些寫操作的處理器之外的處理器來看都是一致的。
    • 帶鎖指令是順序執(zhí)行的。

    ? ? ?值得注意的是,對于Java編譯器而言,Intel 64/IA-32架構(gòu)下處理器不需要LoadLoad、LoadStore、StoreStore屏障,因?yàn)椴粫?huì)發(fā)生需要這三種屏障的重排序。

    七、一例Intel 64/IA-32架構(gòu)下的代碼性能優(yōu)化

    ? ? ? 現(xiàn)在有這樣一個(gè)場景,一個(gè)容器可以放一個(gè)東西,容器支持create方法來創(chuàng)建一個(gè)新的東西并放到容器里,支持get方法取到這個(gè)容器里的東西。我們可以較容易地寫出下面的代碼。

    1 public class Container {2 public static class SomeThing {3 private int status;4 5 public SomeThing() {6 status = 1;7 }8 9 public int getStatus() { 10 return status; 11 } 12 } 13 14 private SomeThing object; 15 16 public void create() { 17 object = new SomeThing(); 18 } 19 20 public SomeThing get() { 21 while (object == null) { 22 Thread.yield(); //不加這句話可能會(huì)在此出現(xiàn)無限循環(huán) 23 } 24 return object; 25 } 26 }

    ? ? ? 在單線程場景下,這段代碼執(zhí)行起來是沒有問題的。但是在多線程并發(fā)場景下,由不同的線程create和get東西,這段代碼是有問題的。問題的原因與普通的雙重檢查鎖定單例模式(Double Checked Locking, DCL)10類似,即SomeThing的構(gòu)建與將指向構(gòu)建中的SomeThing引用賦值到object變量這兩者可能會(huì)發(fā)生重排序。導(dǎo)致get中返回一個(gè)正被構(gòu)建中的不完整的SomeThing對象實(shí)例。為了解決這一問題,通常的辦法是使用volatile修飾object字段。這種方法避免了重排序,保證了內(nèi)存可見性,摒棄比使用同步塊導(dǎo)致的性能損失更小。但是,假如使用場景對object的內(nèi)存可見性并不敏感的話(不要求一個(gè)線程寫入了object,object的新值立即對下一個(gè)讀取的線程可見),在Intel 64/IA-32環(huán)境下,有更好的解決方案。

    ? ? ?根據(jù)上一章的內(nèi)容,我們知道Intel 64/IA-32下寫操作之間不會(huì)發(fā)生重排序,即在處理器中,構(gòu)建SomeThing對象與賦值到object這兩個(gè)操作之間的順序性是可以保證的。這樣看起來,僅僅使用volatile來避免重排序是多此一舉的。但是,Java編譯器卻可能生成重排序后的指令。但令人高興的是,Oracle的JDK中提供了Unsafe. putOrderedObject,Unsafe. putOrderedInt,Unsafe. putOrderedLong這三個(gè)方法,JDK會(huì)在執(zhí)行這三個(gè)方法時(shí)插入StoreStore內(nèi)存屏障,避免發(fā)生寫操作重排序。而在Intel 64/IA-32架構(gòu)下,StoreStore屏障并不需要,Java編譯器會(huì)將StoreStore屏障去除。比起寫入volatile變量之后執(zhí)行StoreLoad屏障的巨大開銷,采用這種方法除了避免重排序而帶來的性能損失以外,不會(huì)帶來其它的性能開銷。
    ? ? ?我們將做一個(gè)小實(shí)驗(yàn)來比較二者的性能差異。一種是使用volatile修飾object成員變量。

    1 public class Container {2 public static class SomeThing {3 private int status;4 5 public SomeThing() {6 status = 1;7 }8 9 public int getStatus() { 10 return status; 11 } 12 } 13 14 private volatile SomeThing object; 15 16 public void create() { 17 object = new SomeThing(); 18 } 19 20 public SomeThing get() { 21 while (object == null) { 22 Thread.yield(); //不加這句話可能會(huì)在此出現(xiàn)無限循環(huán) 23 } 24 return object; 25 } 26 }

    ? ? ?一種是利用Unsafe. putOrderedObject在避免在適當(dāng)?shù)奈恢冒l(fā)生重排序。

    1 public class Container {2 public static class SomeThing {3 private int status;4 5 public SomeThing() {6 status = 1;7 }8 9 public int getStatus() { 10 return status; 11 } 12 } 13 14 private SomeThing object; 15 16 private Object value; 17 private static final Unsafe unsafe = getUnsafe(); 18 private static final long valueOffset; 19 static { 20 try { 21 valueOffset = unsafe.objectFieldOffset(Container.class.getDeclaredField("value")); 22 } catch (Exception ex) { throw new Error(ex); } 23 } 24 25 public void create() { 26 SomeThing temp = new SomeThing(); 27 unsafe.putOrderedObject(this, valueOffset, null); //將value賦null值只是一項(xiàng)無用操作,實(shí)際利用的是這條語句的內(nèi)存屏障 28 object = temp; 29 } 30 31 public SomeThing get() { 32 while (object == null) { 33 Thread.yield(); 34 } 35 return object; 36 } 37 38 39 public static Unsafe getUnsafe() { 40 try { 41 Field f = Unsafe.class.getDeclaredField("theUnsafe"); 42 f.setAccessible(true); 43 return (Unsafe)f.get(null); 44 } catch (Exception e) { 45 } 46 return null; 47 } 48 }

    ? ? ?由于直接調(diào)用Unsafe.getUnsafe()需要配置JRE獲取較高權(quán)限,我們利用反射獲取Unsafe中的theUnsafe來取得Unsafe的可用實(shí)例。
    ? ? ?unsafe.putOrderedObject(this, valueOffset, null)
    ? ? ?這句僅僅是為了借用這句話功能的防止寫重排序,除此之外無其它作用。

    ? ? ?利用下面的代碼分別測試兩種方案的實(shí)際運(yùn)行時(shí)間。在運(yùn)行時(shí)開啟-server和 -XX:CompileThreshold=1以模擬生產(chǎn)環(huán)境下長時(shí)間運(yùn)行后的JIT優(yōu)化效果。

    1 public static void main(String[] args) throws InterruptedException {2 final int THREADS_COUNT = 20;3 final int LOOP_COUNT = 100000;4 5 long sum = 0;6 long min = Integer.MAX_VALUE;7 long max = 0;8 for(int n = 0;n <= 100;n++) {9 final Container basket = new Container(); 10 List<Thread> putThreads = new ArrayList<Thread>(); 11 List<Thread> takeThreads = new ArrayList<Thread>(); 12 for (int i = 0; i < THREADS_COUNT; i++) { 13 putThreads.add(new Thread() { 14 @Override 15 public void run() { 16 for (int j = 0; j < LOOP_COUNT; j++) { 17 basket.create(); 18 } 19 } 20 }); 21 takeThreads.add(new Thread() { 22 @Override 23 public void run() { 24 for (int j = 0; j < LOOP_COUNT; j++) { 25 basket.get().getStatus(); 26 } 27 } 28 }); 29 } 30 long start = System.nanoTime(); 31 for (int i = 0; i < THREADS_COUNT; i++) { 32 takeThreads.get(i).start(); 33 putThreads.get(i).start(); 34 } 35 for (int i = 0; i < THREADS_COUNT; i++) { 36 takeThreads.get(i).join(); 37 putThreads.get(i).join(); 38 } 39 long end = System.nanoTime(); 40 long period = end - start; 41 if(n == 0) { 42 continue; //由于JIT的編譯,第一次執(zhí)行需要更多時(shí)間,將此時(shí)間不計(jì)入統(tǒng)計(jì) 43 } 44 sum += (period); 45 System.out.println(period); 46 if(period < min) { 47 min = period; 48 } 49 if(period > max) { 50 max = period; 51 } 52 } 53 System.out.println("Average : " + sum / 100); 54 System.out.println("Max : " + max); 55 System.out.println("Min : " + min); 56 }

    在筆者的計(jì)算機(jī)上運(yùn)行測試,采用volatile方案的運(yùn)行結(jié)果如下
    Average : 62535770
    Max : 82515000
    Min : 45161000

    采用unsafe.putOrderedObject方案的運(yùn)行結(jié)果如下
    Average : 50746230
    Max : 68999000
    Min : 38038000

    ? ? ? 從結(jié)果看出,unsafe.putOrderedObject方案比volatile方案平均耗時(shí)減少18.9%,最大耗時(shí)減少16.4%,最小耗時(shí)減少15.8%.另外,即使在其它會(huì)發(fā)生寫寫重排序的處理器中,由于StoreStore屏障的性能損耗小于StoreLoad屏障,采用這一方法也是一種可行的方案。但值得再次注意的是,這一方案不是對volatile語義的等價(jià)替換,而是在特定場景下做的特殊優(yōu)化,它僅避免了寫寫重排序,但不保證內(nèi)存可見性。

    ?

    ###附1 復(fù)現(xiàn)重排序現(xiàn)象實(shí)驗(yàn)代碼

    1 public class Test {2 private static int x = 0, y = 0;3 private static int a = 0, b =0;4 5 public static void main(String[] args) throws InterruptedException {6 int i = 0;7 for(;;) {8 i++;9 x = 0; y = 0; 10 a = 0; b = 0; 11 Thread one = new Thread(new Runnable() { 12 public void run() { 13 //由于線程one先啟動(dòng),下面這句話讓它等一等線程two. 讀著可根據(jù)自己電腦的實(shí)際性能適當(dāng)調(diào)整等待時(shí)間. 14 shortWait(100000); 15 a = 1; 16 x = b; 17 } 18 }); 19 20 Thread other = new Thread(new Runnable() { 21 public void run() { 22 b = 1; 23 y = a; 24 } 25 }); 26 one.start();other.start(); 27 one.join();other.join(); 28 String result = "第" + i + "次 (" + x + "," + y + ")"; 29 if(x == 0 && y == 0) { 30 System.err.println(result); 31 break; 32 } else { 33 System.out.println(result); 34 } 35 } 36 } 37 38 39 public static void shortWait(long interval){ 40 long start = System.nanoTime(); 41 long end; 42 do{ 43 end = System.nanoTime(); 44 }while(start + interval >= end); 45 } 46 }from: https://blog.csdn.net/qq_29923439/article/details/51273812

    總結(jié)

    以上是生活随笔為你收集整理的JVM内存模型、指令重排、内存屏障概念解析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。