1. JVM的概念
JVM是Java Virtual Machine(Java虛擬機(jī))的縮寫溯饵,JVM是一種用于計(jì)算設(shè)備的規(guī)范,它是一個(gè)虛構(gòu)出來的計(jì)算機(jī)锨用,是通過在實(shí)際的計(jì)算機(jī)上仿真模擬各種計(jì)算機(jī)功能來實(shí)現(xiàn)的丰刊。
Java語(yǔ)言的一個(gè)非常重要的特點(diǎn)就是與平臺(tái)的無關(guān)性。而使用Java虛擬機(jī)是實(shí)現(xiàn)這一特點(diǎn)的關(guān)鍵增拥。一般的高級(jí)語(yǔ)言如果要在不同的平臺(tái)上運(yùn)行啄巧,至少需要編譯成不同的目標(biāo)代碼。而引入Java語(yǔ)言虛擬機(jī)后掌栅,Java語(yǔ)言在不同平臺(tái)上運(yùn)行時(shí)不需要重新編譯秩仆。Java語(yǔ)言使用Java虛擬機(jī)屏蔽了與具體平臺(tái)相關(guān)的信息,使得Java語(yǔ)言編譯程序只需生成在Java虛擬機(jī)上運(yùn)行的目標(biāo)代碼(字節(jié)碼)猾封,就可以在多種平臺(tái)上不加修改地運(yùn)行澄耍。Java虛擬機(jī)在執(zhí)行字節(jié)碼時(shí),把字節(jié)碼解釋成具體平臺(tái)上的機(jī)器指令執(zhí)行。這就是Java的能夠“一次編譯齐莲,到處運(yùn)行”的原因痢站。
2. JMM的概念
JMM是Java Memory Model(Java內(nèi)存模型)的縮寫。JMM定義了Java 虛擬機(jī)(JVM)在計(jì)算機(jī)內(nèi)存(RAM)中的工作方式选酗。JVM是整個(gè)計(jì)算機(jī)虛擬模型阵难,所以JMM是隸屬于JVM的。Java線程之間的通信由JMM控制芒填,JMM決定一個(gè)線程對(duì)共享變量的寫入何時(shí)對(duì)另一個(gè)線程可見呜叫。
如果我們要想深入了解Java并發(fā)編程,就要先理解好Java內(nèi)存模型殿衰。Java內(nèi)存模型定義了多線程之間共享變量的可見性以及如何在需要的時(shí)候?qū)蚕碜兞窟M(jìn)行同步朱庆。
3. 并發(fā)編程模型的分類
在并發(fā)編程中,我們需要處理兩個(gè)關(guān)鍵問題:線程之間如何通信及線程之間如何同步播玖。
- 線程通信: 是指線程之間以何種機(jī)制來交換信息
- 線程同步: 是指程序用于控制不同線程之間操作發(fā)生相對(duì)順序的機(jī)制
在命令式編程中椎工,線程之間的通信機(jī)制有兩種:
- 共享內(nèi)存模型
- 消息傳遞模型
3.1 共享內(nèi)存模型
線程通信 在共享內(nèi)存的并發(fā)模型里,線程之間共享程序的公共狀態(tài)蜀踏,線程之間通過寫-讀內(nèi)存中的公共狀態(tài)來隱式進(jìn)行通信维蒙,典型的共享內(nèi)存通信方式就是通過共享對(duì)象進(jìn)行通信。
線程同步 在共享內(nèi)存的并發(fā)模型里果覆,同步是顯式進(jìn)行的颅痊。程序員必須顯式指定某個(gè)方法或某段代碼需要在線程之間互斥執(zhí)行。
3.2 消息傳遞模型
線程通信 在消息傳遞的并發(fā)模型里局待,線程之間沒有公共狀態(tài)斑响,線程之間必須通過明確的發(fā)送消息來顯式進(jìn)行通信,在java中典型的消息傳遞方式就是wait()和notify()钳榨。
線程同步 在消息傳遞的并發(fā)模型里舰罚,由于消息的發(fā)送必須在消息的接收之前,因此同步是隱式進(jìn)行的薛耻。
4. Java的并發(fā)采用的是共享內(nèi)存模型
Java線程之間的通信總是隱式進(jìn)行营罢,整個(gè)通信過程對(duì)程序員完全透明。如果編寫多線程程序的Java程序員不理解隱式進(jìn)行的線程之間通信的工作機(jī)制饼齿,很可能會(huì)遇到各種奇怪的內(nèi)存可見性問題饲漾。
5. Java內(nèi)存模型
Java內(nèi)存模型(簡(jiǎn)稱JMM)定義了線程和主內(nèi)存之間的抽象關(guān)系:
主內(nèi)存 根據(jù)JMM的設(shè)計(jì),系統(tǒng)存在一個(gè)主內(nèi)存(Main Memory)缕溉,Java中所有共享變量都儲(chǔ)存在主內(nèi)存中考传,對(duì)于所有線程都是共享的。
本地內(nèi)存 每條線程都有自己的本地內(nèi)存(Local Memory)证鸥,本地內(nèi)存中存儲(chǔ)了該線程以讀/寫共享變量的副本僚楞。線程對(duì)所有變量的操作都是在本地內(nèi)存中進(jìn)行勤晚,線程之間無法相互直接訪問,變量傳遞均需要通過主內(nèi)存完成镜硕。
本地內(nèi)存是JMM的一個(gè)抽象概念运翼,并不真實(shí)存在。它涵蓋了緩存兴枯,寫緩沖區(qū),寄存器以及其他的硬件和編譯器優(yōu)化矩欠。
從上圖來看财剖,線程A與線程B之間如要通信的話,必須要經(jīng)歷下面2個(gè)步驟:
1. 首先癌淮,線程A把本地內(nèi)存A中更新過的共享變量刷新到主內(nèi)存中去躺坟。
2. 然后,線程B到主內(nèi)存中去讀取線程A之前已更新過的共享變量乳蓄。
從整體來看咪橙,這兩個(gè)步驟實(shí)質(zhì)上是線程A在向線程B發(fā)送消息,而且這個(gè)通信過程必須要經(jīng)過主內(nèi)存虚倒。JMM通過控制主內(nèi)存與每個(gè)線程的本地內(nèi)存之間的交互美侦,來為Java程序員提供內(nèi)存可見性保證。
上面也說到了魂奥,Java內(nèi)存模型只是一個(gè)抽象概念菠剩,那么它在Java中具體是怎么工作的呢?為了更好的理解Java內(nèi)存模型工作方式耻煤,下面就JVM對(duì)Java內(nèi)存模型的實(shí)現(xiàn)具壮、硬件內(nèi)存模型及它們之間的橋接做詳細(xì)介紹。
6. Java內(nèi)存模型在JVM中的實(shí)現(xiàn)
關(guān)于JVM對(duì)Java內(nèi)存模型的實(shí)現(xiàn)哈蝇,我們從JVM的邏輯視圖棺妓,硬件架構(gòu),CUP指令等多個(gè)角度進(jìn)行解讀炮赦。
6.1 JVM邏輯視圖
在JVM內(nèi)部怜跑,Java內(nèi)存模型把內(nèi)存分成了兩部分:線程棧區(qū)和堆區(qū),下圖展示了Java內(nèi)存模型在JVM中的邏輯視圖:
Thread Stack JVM中運(yùn)行的每個(gè)線程都擁有自己的線程棧眼五,線程棧包含了當(dāng)前線程執(zhí)行的方法調(diào)用相關(guān)信息妆艘,我們也把它稱作調(diào)用棧破加。隨著代碼的不斷執(zhí)行谱轨,調(diào)用棧會(huì)不斷變化。
線程棧還包含了當(dāng)前方法的所有本地變量信息供璧。所有原始類型(boolean,byte,short,char,int,long,float,double)的本地變量都直接保存在線程棧當(dāng)中诵姜,對(duì)于它們的值各個(gè)線程之間都是獨(dú)立的汽煮。也就是說,一個(gè)線程只能讀取自己的線程棧,線程中的本地變量對(duì)其它線程是不可見的暇赤。即使兩個(gè)線程執(zhí)行的是同一段代碼心例,它們也會(huì)各自在自己的線程棧中創(chuàng)建本地變量。Heap 堆區(qū)包含了Java應(yīng)用創(chuàng)建的所有對(duì)象信息鞋囊,不管對(duì)象是哪個(gè)線程創(chuàng)建的止后,其中的對(duì)象包括原始類型的封裝類(如Byte、Integer溜腐、Long等等)译株。
堆中的對(duì)象可以被多線程共享。如果一個(gè)線程獲得一個(gè)對(duì)象的引用挺益,它便可訪問這個(gè)對(duì)象的成員變量歉糜。如果兩個(gè)線程同時(shí)調(diào)用了同一個(gè)對(duì)象的同一個(gè)方法,那么這兩個(gè)線程便可同時(shí)訪問這個(gè)對(duì)象的成員變量望众,但是對(duì)于本地變量匪补,每個(gè)線程都會(huì)拷貝一份到自己的線程棧中。
下圖展示了上面描述的過程:
關(guān)于棧區(qū)和堆區(qū)的存儲(chǔ)內(nèi)容烂翰,請(qǐng)參考下面的原則:
- 一個(gè)本地變量如果是原始類型夯缺,那么它會(huì)被完全存儲(chǔ)到棧區(qū)
- 一個(gè)本地變量也有可能是一個(gè)對(duì)象的引用,這種情況下刽酱,這個(gè)本地引用會(huì)被存儲(chǔ)到棧中喳逛,但是對(duì)象本身仍然存儲(chǔ)在堆區(qū)
- 對(duì)于一個(gè)對(duì)象的成員方法,這些方法中包含本地變量棵里,仍需要存儲(chǔ)在棧區(qū)润文,即使它們所屬的對(duì)象在堆區(qū)
- 對(duì)于一個(gè)對(duì)象的成員變量,不管它是原始類型還是包裝類型殿怜,都會(huì)被存儲(chǔ)到堆區(qū)
- Static類型的變量以及類本身相關(guān)信息都會(huì)隨著類本身存儲(chǔ)在堆區(qū)
JVM參數(shù)對(duì)于內(nèi)存的設(shè)置
-
堆內(nèi)存:
- -Xms 初始分配堆內(nèi)存 默認(rèn)是物理內(nèi)存的1/64
- -Xmx 最大堆內(nèi)存 默認(rèn)是物理內(nèi)存的1/4
- 【增長(zhǎng)策略】
默認(rèn)空余堆內(nèi)存小于40%時(shí)典蝌,JVM就會(huì)增大堆直到-Xmx的最大限制
空余堆內(nèi)存大于70%時(shí),JVM會(huì)減少堆直到-Xms的最小限制
因此服務(wù)器一般設(shè)置-Xms头谜、-Xmx 相等以避免在每次GC 后調(diào)整堆的大小骏掀。 - 【注意事項(xiàng)】
如果-Xmx 不指定或者指定偏小,應(yīng)用可能會(huì)導(dǎo)致java.lang.OutOfMemory錯(cuò)誤柱告,此錯(cuò)誤來自JVM截驮,不是Throwable的,無法用try…catch捕捉际度。
-
非堆 Non-heap 內(nèi)存
- -XX:PermSize 設(shè)置非堆內(nèi)存初始值葵袭,其縮寫為permanent size(持久化內(nèi)存),默認(rèn)是物理內(nèi)存的1/64
- -XX:MaxPermSize 設(shè)置最大非堆內(nèi)存的大小乖菱,默認(rèn)是物理內(nèi)存的1/4
- 【注意事項(xiàng)】
非堆內(nèi)存不會(huì)被java垃圾回收機(jī)制進(jìn)行處理
6.2 計(jì)算機(jī)硬件內(nèi)存架構(gòu)
不管是什么內(nèi)存模型坡锡,最終還是運(yùn)行在計(jì)算機(jī)硬件上的蓬网,所以我們有必要了解計(jì)算機(jī)硬件內(nèi)存架構(gòu),下圖就簡(jiǎn)單描述了當(dāng)代計(jì)算機(jī)硬件內(nèi)存架構(gòu):
CUP 現(xiàn)代計(jì)算機(jī)一般都有2個(gè)以上CPU鹉勒,而且每個(gè)CPU還有可能包含多個(gè)核心帆锋。因此,如果我們的應(yīng)用是多線程的話禽额,這些線程可能會(huì)在各個(gè)CPU核心中并行運(yùn)行锯厢。
CUP Registers 在CPU內(nèi)部有一組CPU寄存器,也就是CPU的儲(chǔ)存器绵疲。CPU操作寄存器的速度要比操作計(jì)算機(jī)主存快的多哲鸳。
CUP Cache Memory 在主存和CPU寄存器之間還存在一個(gè)CPU緩存,CPU操作CPU緩存的速度快于主存但慢于CPU寄存器盔憨。某些CPU可能有多個(gè)緩存層(一級(jí)緩存和二級(jí)緩存)。
RAM - Main Memory 計(jì)算機(jī)的主存也稱作RAM讯沈,所有的CPU都能夠訪問主存郁岩,而且主存比上面提到的緩存和寄存器大很多。
當(dāng)一個(gè)CPU需要訪問主存時(shí)缺狠,會(huì)先讀取一部分主存數(shù)據(jù)到CPU緩存问慎,進(jìn)而在讀取CPU緩存到寄存器。
當(dāng)一個(gè)CPU需要寫數(shù)據(jù)到主存時(shí)挤茄,同樣會(huì)先flush寄存器到CPU緩存如叼,然后再在某些節(jié)點(diǎn)把緩存數(shù)據(jù)flush到主存。
6.3 Java內(nèi)存模型和硬件架構(gòu)之間的橋接
從上面可以看出穷劈,Java內(nèi)存模型和硬件內(nèi)存架構(gòu)并不一致笼恰。硬件內(nèi)存架構(gòu)中并沒有區(qū)分棧和堆,從硬件上看歇终,不管是棧還是堆社证,大部分?jǐn)?shù)據(jù)都會(huì)存到主存中,當(dāng)然一部分棧和堆的數(shù)據(jù)也有可能會(huì)存到CPU寄存器中评凝,如下圖所示追葡,Java內(nèi)存模型和計(jì)算機(jī)硬件內(nèi)存架構(gòu)是一個(gè)交叉關(guān)系:
6.4 多線程產(chǎn)生的內(nèi)存問題
在我們進(jìn)行多線程編程時(shí),對(duì)象和變量存儲(chǔ)到計(jì)算機(jī)的各個(gè)內(nèi)存區(qū)域的奕短,這樣必然會(huì)面臨一些問題宜肉,其中最主要的兩個(gè)問題是:
1. 共享對(duì)象對(duì)各個(gè)線程的可見性
2. 共享對(duì)象的競(jìng)爭(zhēng)現(xiàn)象
下面詳細(xì)我們說一下這兩個(gè)問題,并了解一下Java的是怎么解決這些問題的
6.4.1 共享對(duì)象的可見性
假設(shè)我們的共享對(duì)象存儲(chǔ)在主存翎碑,一個(gè)CPU中的線程讀取主存數(shù)據(jù)到CPU緩存谬返,然后對(duì)共享對(duì)象做了更改,但CPU緩存中的更改后的對(duì)象還沒有flush到主存杈女,此時(shí)這個(gè)線程對(duì)共享對(duì)象的更改對(duì)其它CPU中的線程是不可見的朱浴。最終就是每個(gè)線程最終都會(huì)拷貝共享對(duì)象吊圾,而且拷貝的對(duì)象位于不同的CPU緩存中。
下圖展示了上面描述的過程翰蠢。左邊CPU中運(yùn)行的線程從主存中拷貝共享對(duì)象obj到它的CPU緩存项乒,把對(duì)象obj的count變量改為2。但這個(gè)變更對(duì)運(yùn)行在右邊CPU中的線程不可見梁沧,因?yàn)檫@個(gè)更改還沒有flush到主存中:
要解決共享對(duì)象可見性這個(gè)問題檀何,我們可以使用Java的volatile
關(guān)鍵字。 volatile
關(guān)鍵字可以保證變量會(huì)直接從主存讀取廷支,而對(duì)變量的更新也會(huì)直接寫到主存频鉴。volatile原理 是基于CPU內(nèi)存屏障指令實(shí)現(xiàn)的,后面會(huì)講到恋拍。
6.4.2 競(jìng)爭(zhēng)現(xiàn)象
如果多個(gè)線程共享一個(gè)對(duì)象垛孔,如果它們要同時(shí)修改這個(gè)共享對(duì)象,這就產(chǎn)生了競(jìng)爭(zhēng)現(xiàn)象施敢。
如下圖所示周荐,線程A和線程B共享一個(gè)對(duì)象obj。假設(shè)線程A從主存讀取Obj.count變量到自己的CPU緩存僵娃,同時(shí)概作,線程B也讀取了Obj.count變量到它的CPU緩存,并且這兩個(gè)線程都對(duì)Obj.count做了加1操作默怨。此時(shí)讯榕,Obj.count加1操作被執(zhí)行了兩次,不過都在不同的CPU緩存中匙睹。
如果這兩個(gè)加1操作是串行執(zhí)行的愚屁,那么Obj.count變量便會(huì)在原始值上加2,最終主存中的Obj.count的值會(huì)是3垃僚。然而下圖中兩個(gè)加1操作是并行的集绰,不管是線程A還是線程B先flush計(jì)算結(jié)果到主存,最終主存中的Obj.count只會(huì)增加1次變成2谆棺,盡管一共有兩次加1操作栽燕。
要解決上面的問題我們可以使用synchronized
代碼塊。synchronized
代碼塊可以保證同一個(gè)時(shí)刻只能有一個(gè)線程進(jìn)入代碼競(jìng)爭(zhēng)區(qū)改淑,synchronized
代碼塊也能保證代碼塊中所有變量都將會(huì)從主存中讀碍岔,當(dāng)線程退出代碼塊時(shí),對(duì)所有變量的更新將會(huì)flush到主存朵夏,不管這些變量是不是volatile
類型的蔼啦。
volatile和synchronized的區(qū)別
- volatile本質(zhì)是在告訴JVM當(dāng)前變量在寄存器(本地內(nèi)存)中的值是不確定的,需要從主存中讀妊霾捏肢; synchronized則是鎖定當(dāng)前變量奈籽,只有當(dāng)前線程可以訪問該變量,其他線程被阻塞住鸵赫。
- volatile僅能使用在變量級(jí)別衣屏;synchronized則可以使用在變量、方法辩棒、和類級(jí)別的
- volatile僅能實(shí)現(xiàn)變量的修改可見性狼忱,不能保證原子性;而synchronized則可以保證變量的修改可見性和原子性
- volatile不會(huì)造成線程的阻塞一睁;synchronized可能會(huì)造成線程的阻塞钻弄。
- volatile標(biāo)記的變量不會(huì)被編譯器優(yōu)化;synchronized標(biāo)記的變量可以被編譯器優(yōu)化
6.5 JMM深層解析
6.5.1 重排序
重排序通常是編譯器或運(yùn)行時(shí)環(huán)境為了優(yōu)化程序性能而采取的對(duì)指令進(jìn)行重新排序執(zhí)行的一種手段者吁。但是窘俺,重排序可能會(huì)導(dǎo)致程序執(zhí)行的結(jié)果不是我們需要的結(jié)果。重排序分三種類型:
- 編譯器優(yōu)化的重排序 編譯器在不改變單線程程序語(yǔ)義的前提下复凳,可以重新安排語(yǔ)句的執(zhí)行順序批销。
- 指令級(jí)并行的重排序 現(xiàn)代處理器采用了指令級(jí)并行技術(shù)(Instruction-Level Parallelism, ILP)來將多條指令重疊執(zhí)行染坯。如果不存在數(shù)據(jù)依賴性,處理器可以改變語(yǔ)句對(duì)應(yīng)機(jī)器指令的執(zhí)行順序丘逸。
- 內(nèi)存系統(tǒng)的重排序 由于處理器使用緩存和讀/寫緩沖區(qū)单鹿,這使得加載和存儲(chǔ)操作看上去可能是在亂序執(zhí)行。
從Java源代碼到最終實(shí)際執(zhí)行的指令序列深纲,會(huì)分別經(jīng)歷下面三種重排序:
上述的1屬于編譯器重排序仲锄,2和3屬于處理器重排序。這些重排序都可能會(huì)導(dǎo)致多線程程序出現(xiàn)內(nèi)存可見性問題湃鹊。
6.5.2 Memory Barrier 內(nèi)存屏障指令
內(nèi)存屏障儒喊,又稱內(nèi)存柵欄,是一個(gè)CPU指令币呵,基本上它是一條這樣的指令:
1怀愧、保證特定操作的執(zhí)行順序。
2余赢、影響某些數(shù)據(jù)(或則是某條指令的執(zhí)行結(jié)果)的內(nèi)存可見性芯义。
編譯器和處理器能夠重排序指令,保證最終相同的結(jié)果妻柒,嘗試優(yōu)化性能扛拨。但插入一條Memory Barrier會(huì)告訴編譯器和處理器:不管什么指令都不能和這條Memory Barrier指令重排序。
Memory Barrier所做的另外一件事是強(qiáng)制刷出各種CPU cache举塔,如一個(gè) Write-Barrier(寫入屏障)將刷出所有在 Barrier 之前寫入 cache 的數(shù)據(jù)绑警,因此求泰,任何CPU上的線程都能讀取到這些數(shù)據(jù)的最新版本。
volatile
volatile
是基于Memory Barrier實(shí)現(xiàn)的计盒。
如果一個(gè)變量是volatile
修飾的渴频,JMM會(huì)在寫入這個(gè)字段之后插進(jìn)一個(gè)Write-Barrier指令,并在讀這個(gè)字段之前插入一個(gè)Read-Barrier指令章郁。
這意味著枉氮,如果寫入一個(gè)volatile
變量a,可以保證:
- 一個(gè)線程寫入變量a后暖庄,任何線程訪問該變量都會(huì)拿到最新值聊替。
- 在寫入變量a之前的寫入操作,其更新的數(shù)據(jù)對(duì)于其他線程也是可見的培廓。因?yàn)镸emory Barrier會(huì)刷出cache中的所有先前的寫入惹悄。
6.5.3 happens-before
從JDK5開始,Java使用新的JSR -133內(nèi)存模型肩钠。JSR-133使用happens-before的概念來指定兩個(gè)操作之間的執(zhí)行順序泣港。由于這兩個(gè)操作可以在一個(gè)線程之內(nèi),也可以是在不同線程之間价匠。因此当纱,JMM可以通過happens-before關(guān)系向程序員提供跨線程的內(nèi)存可見性保證
我們通過代碼示例來了解一下什么是 happens-before
double pi = 3.14; // A
double r = 1.0; // B
double area = pi * r * r; // C
上述計(jì)算圓的面積的代碼中,存在三個(gè)happens-before關(guān)系:
- A happens-before B
- B happens-before C
- A happens-before C
在者三個(gè)happens-before關(guān)系中2和3是必須的踩窖,1是不必要的坡氯。因此JMM把happens-before要求禁止的重排序分了下面兩類:
- 會(huì)改變程序執(zhí)行結(jié)果的重排序
- 不會(huì)改變程序執(zhí)行結(jié)果的重排序
JMM對(duì)這兩種不同性質(zhì)的重排序,采用了不同的策略洋腮,如下:
- 對(duì)于會(huì)改變程序執(zhí)行結(jié)果的重排序箫柳,JMM要求編譯器和處理器必須禁止這種重排序
- 對(duì)于不會(huì)改變程序執(zhí)行結(jié)果的重排序,JMM對(duì)編譯器和處理器不做要求啥供,也就是說JMM允許這種重排序
下圖是JMM的設(shè)計(jì)示意圖
JMM對(duì)編譯器和處理器的束縛已經(jīng)盡可能少悯恍。從上面的分析可以看出,JMM其實(shí)是在遵循一個(gè)基本原則:只要不改變程序的執(zhí)行結(jié)果(指的是單線程程序和正確同步的多線程程序)伙狐,編譯器和處理器怎么優(yōu)化都行涮毫。例如,如果編譯器經(jīng)過細(xì)致的分析后鳞骤,認(rèn)定一個(gè)鎖只會(huì)被單個(gè)線程訪問窒百,那么這個(gè)鎖可以被消除。再如豫尽,如果編譯器經(jīng)過細(xì)致的分析后篙梢,認(rèn)定一個(gè)volatile變量只會(huì)被單個(gè)線程訪問,那么編譯器可以把這個(gè)volatile變量當(dāng)作一個(gè)普通變量來對(duì)待美旧。這些優(yōu)化既不會(huì)改變程序的執(zhí)行結(jié)果渤滞,又能提高程序的執(zhí)行效率贬墩。
happens-before規(guī)則
- 程序順序規(guī)則:一個(gè)線程中的每個(gè)操作,happens-before 于該線程中的任意后續(xù)操作
- 監(jiān)視器鎖規(guī)則:對(duì)一個(gè)監(jiān)視器鎖的解鎖妄呕,happens-before 于隨后對(duì)這個(gè)監(jiān)視器鎖的加鎖
- volatile變量規(guī)則:對(duì)一個(gè)volatile域的寫陶舞,happens-before 于任意后續(xù)對(duì)這個(gè)volatile域的讀
- 傳遞性:如果A happens- before B,且B happens-before C绪励,那么A happens-before C
- start規(guī)則:如果線程A執(zhí)行操作ThreadB.start()(啟動(dòng)線程B)肿孵,那么A線程的ThreadB.start()操作happens-before于線程B中的任意操作
- join規(guī)則:如果線程A執(zhí)行操作ThreadB.join()并成功返回,那么線程B中的任意操作happens-before于線程A從ThreadB.join()操作成功返回疏魏。
對(duì)于Java程序員來說停做,happens-before規(guī)則簡(jiǎn)單易懂,它避免程序員為了理解JMM提供的內(nèi)存可見性保證而去學(xué)習(xí)復(fù)雜的重排序規(guī)則以及這些規(guī)則的具體實(shí)現(xiàn)大莫。