欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

必須要學會的JMM與volatile

 更新時間:2022年09月19日 10:56:07   作者:Wang1???????  
這篇文章主要介紹了必須要學會的JMM與volatile,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下

1. JAVA 內(nèi)存模型 (JMM)

  • JMM是用來干嘛的?:《Java虛擬機規(guī)范》中曾試圖定義一種“Java內(nèi)存模型”(Java Memory Model,JMM)來屏蔽各種硬件和操作系統(tǒng)的內(nèi)存訪問差異,以實現(xiàn)讓Java程序在各種平臺下都能達到一致的內(nèi)存訪問效果。
  • 主要目的是什么?:定義程序中各種變量的訪問規(guī)則,即關(guān)注在虛擬機中把變量值存儲到內(nèi)存和從內(nèi)存中取出變量值這樣的底層細節(jié)。此處的變量(Variables)與Java編程中所說的變量有所區(qū)別,它包括了實例字段靜態(tài)字段構(gòu)成數(shù)組對象的元素,但是包括局部變量與方法參數(shù),因為后者是線程私有的,不會被共享,自然就不會存在競爭問題。

1.1 主內(nèi)存與工作內(nèi)存

Java內(nèi)存模型規(guī)定了所有的變量都存儲在主內(nèi)存(Main Memory)中。每條線程還有自己的工作內(nèi)存(Working Memory),線程的工作內(nèi)存中保存了被該線程使用的變量的主內(nèi)存副本,線程對變量的所有操作(讀取、賦值等)都必須在工作內(nèi)存中進行,而不能直接讀寫主內(nèi)存中的數(shù)據(jù)。不同的線程之間也無法直接訪問對方工作內(nèi)存中的變量,線程間變量值的傳遞均需要通過主內(nèi)存來完成。

這里所講的主內(nèi)存、工作內(nèi)存與Java內(nèi)存區(qū)域中的Java堆、棧、方法區(qū)等并不是同一個層次的對內(nèi)存的劃分,這兩者基本上是沒有任何關(guān)系的。如果兩者一定要勉強對應(yīng)起來,那么從變量、主內(nèi)存、工作內(nèi)存的定義來看,主內(nèi)存主要對應(yīng)于Java堆中的對象實例數(shù)據(jù)部分,而工作內(nèi)存則對應(yīng)于虛擬機棧中的部分區(qū)域。

從更基礎(chǔ)的層次上說,主內(nèi)存直接對應(yīng)于物理硬件的內(nèi)存,而為了獲取更好的運行速度,虛擬機(或者是硬件、操作系統(tǒng)本身的優(yōu)化措施)可能會讓工作內(nèi)存優(yōu)先存儲于寄存器和高速緩存中,因為程序運行時主要訪問的是工作內(nèi)存。

1.2 內(nèi)存間的交互

關(guān)于主內(nèi)存與工作內(nèi)存之間具體的交互協(xié)議,即一個變量如何從主內(nèi)存拷貝到工作內(nèi)存、如何從工作內(nèi)存同步回主內(nèi)存這一類的實現(xiàn)細節(jié),Java內(nèi)存模型中定義了以下8種操作來完成。Java虛擬機實現(xiàn)時必須保證下面提及的每一種操作都是原子的、不可再分的(對于double和long類型的變量來說,load、store、read和write操作在某些平臺上允許有例外)。

  • lock(鎖定):作用于主內(nèi)存的變量,它把一個變量標識為一條線程獨占的狀態(tài)。
  • unlock(解鎖):作用于主內(nèi)存的變量,它把一個處于鎖定狀態(tài)的變量釋放出來,釋放后的變量才可以被其他線程鎖定。
  • read(讀?。?/strong>作用于主內(nèi)存的變量,它把一個變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中,以便隨后的load動作使用。
  • load(載入):作用于工作內(nèi)存的變量,它把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中。
  • use(使用):作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個變量的值傳遞給執(zhí)行引擎,每當虛擬機遇到一個需要使用變量的值的字節(jié)碼指令時將會執(zhí)行這個操作。
  • assign(賦值):作用于工作內(nèi)存的變量,它把一個從執(zhí)行引擎接收的值賦給工作內(nèi)存的變量,每當虛擬機遇到一個給變量賦值的字節(jié)碼指令時執(zhí)行這個操作。
  • store(存儲):作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個變量的值傳送到主內(nèi)存中,以便隨后的write操作使用。
  • write(寫入):作用于主內(nèi)存的變量,它把store操作從工作內(nèi)存中得到的變量的值放入主內(nèi)存的變量中。

非原子性協(xié)定:

Java內(nèi)存模型要求lock、unlock、read、load、assign、use、store、write這八種操作都具有原子性,但是對于64位的數(shù)據(jù)類型(long和double),在模型中特別定義了一條寬松的規(guī)定:允許虛擬機將沒有被volatile修飾的64位數(shù)據(jù)的讀寫操作劃分為兩次32位的操作來進行,即允許虛擬機實現(xiàn)自行選擇是否要保證64位數(shù)據(jù)類型的load、store、read和write這四個操作的原子性,這就是所謂的“long和double的非原子性協(xié)定”(Non-Atomic Treatment of double and long Variables)。

如果要把一個變量從主內(nèi)存拷貝到工作內(nèi)存,那就要按順序執(zhí)行read和load操作,如果要把變量從工作內(nèi)存同步回主內(nèi)存,就要按順序執(zhí)行store和write操作。注意,Java內(nèi)存模型只要求上述兩個操作必須按順序執(zhí)行,但不要求是連續(xù)執(zhí)行。也就是說read與load之間、store與write之間是可插入其他指令的,如對主內(nèi)存中的變量a、b進行訪問時,一種可能出現(xiàn)的順序是read a、read b、load b、load a。

Java內(nèi)存模型還規(guī)定了在執(zhí)行上述8種基本操作時必須滿足如下規(guī)則:

  • 不允許read和load、store和write操作之一單獨出現(xiàn),即不允許一個變量從主內(nèi)存讀取了但工作內(nèi)存不接受,或者工作內(nèi)存發(fā)起回寫了但主內(nèi)存不接受的情況出現(xiàn)。
  • 不允許一個線程丟棄它最近的assign操作,即變量在工作內(nèi)存中改變了之后必須把該變化同步回主內(nèi)存。
  • 不允許一個線程無原因地(沒有發(fā)生過任何assign操作)把數(shù)據(jù)從線程的工作內(nèi)存同步回主內(nèi)存中。
  • 一個新的變量只能在主內(nèi)存中“誕生”,不允許在工作內(nèi)存中直接使用一個未被初始化(load或assign)的變量,換句話說就是對一個變量實施use、store操作之前,必須先執(zhí)行assign和load操作。
  • 一個變量在同一個時刻只允許一條線程對其進行l(wèi)ock操作,但lock操作可以被同一條線程重復(fù)執(zhí)行多次,多次執(zhí)行l(wèi)ock后,只有執(zhí)行相同次數(shù)的unlock操作,變量才會被解鎖。
  • 如果對一個變量執(zhí)行l(wèi)ock操作,那將會清空工作內(nèi)存中此變量的值,在執(zhí)行引擎使用這個變量前,需要重新執(zhí)行l(wèi)oad或assign操作以初始化變量的值。
  • 如果一個變量事先沒有被lock操作鎖定,那就不允許對它執(zhí)行unlock操作,也不允許去unlock一個被其他線程鎖定的變量。
  • 對一個變量執(zhí)行unlock操作之前,必須先把此變量同步回主內(nèi)存中(執(zhí)行store、write操作)。

2. 關(guān)于 Volatile 變量

Volatile變量具備的三個關(guān)鍵點(保證可見性,不能保證原子性,禁止指令重排序):

  • 用volatile聲明一個變量可以保證對所有線程的可見性,這里的“可見性”是指當一條線程修改了這個變量的值,新值對于其他線程來說是可以及時得知的(并不是立即可見的,從物理存儲的角度看,各個線程的工作內(nèi)存中volatile變量也可以存在不一致的情況,但由于每次使用之前都要先刷新,執(zhí)行引擎看不到不一致的情況,因此可以認為不存在一致性問題))。
  • 基于volatile變量的運算在并發(fā)下并不是線程安全的
  • 禁止指令重排序優(yōu)化,普通的變量僅會保證在該方法的執(zhí)行過程中所有依賴賦值結(jié)果的地方都能獲取到正確的結(jié)果,而不能保證變量賦值操作的順序與程序代碼中的執(zhí)行順序一致。因為在同一個線程的方法執(zhí)行過程中無法感知到這點,這就是Java內(nèi)存模型中描述的所謂“線程內(nèi)表現(xiàn)為串行的語義”(As-If-Serial)。

對于 As-If-Serial 的簡要說明:對于處理器或者編譯器來說,在進行指令重排序優(yōu)化(為了提高并行度)的時候只能保證在單線程環(huán)境下的串行化語義的一致性

舉個栗子,下面一個雙鎖檢測(Double Check Lock,DCL)單例:

public class Singleton {
    private volatile static Singleton instance;
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
    public static void main(String[] args) {
        Singleton.getInstance();
    }
}

對instance變量賦值相關(guān)的字節(jié)碼:

0x01a3de0f: mov $0x3375cdb0,%esi         ;...beb0cd75 33 
                                         ; {oop('Singleton')}
0x01a3de14: mov %eax,0x150(%esi)         ;...89865001 0000
0x01a3de1a: shr $0x9,%esi                   ;...c1ee09
0x01a3de1d: movb $0x0,0x1104800(%esi)     ;...c6860048 100100
0x01a3de24: lock addl $0x0,(%esp)         ;...f0830424 00
                                        ;*putstatic instance
                                        ; - Singleton::getInstance@24

有volatile修飾的變量,賦值后(前面mov%eax,0x150(%esi)這句便是賦值操作)多執(zhí)行了一個 lock addl$0x0,(%esp) 操作,這個操作的作用相當于一個內(nèi)存屏障 (Memory Barrier或Memory Fence,指重排序時不能把后面的指令重排序到內(nèi)存屏障之前的位置),只有一個處理器訪問內(nèi)存時,并不需要內(nèi)存屏障;但如果有兩個或更多處理器訪問同一塊內(nèi)存,且其中有一個在觀測另一個,就需要內(nèi)存屏障來保證一致性了。

這句指令中的 addl$0x0,(%esp) (把ESP寄存器的值加0)顯然是一個空操作,之所以用這個空操作而不是空操作專用指令nop,是因為IA32手冊規(guī)定lock前綴不允許配合nop指令使用。這里的關(guān)鍵在于lock前綴,查詢IA32手冊可知,它的作用是將本處理器的緩存寫入了內(nèi)存,該寫入動作也會引起別的處理器或者別的內(nèi)核無效化(Invalidate)其緩存,這種操作相當于對緩存中的變量做了一次前面介紹Java內(nèi)存模式中所說的“store和write”操作。所以通過這樣一個空操作,可讓前面volatile變量的修改對其他處理器立即可見。

那為何說它禁止指令重排序呢?從硬件架構(gòu)上講,指令重排序是指處理器采用了允許將多條指令不按程序規(guī)定的順序分開發(fā)送給各個相應(yīng)的電路單元進行處理。但并不是說指令任意重排,處理器必須能正確處理指令依賴情況保障程序能得出正確的執(zhí)行結(jié)果。譬如指令1把地址A中的值加10,指令2把地址A中的值乘以2,指令3把地址B中的值減去3,這時指令1和指令2是有依賴的,它們之間的順序不能重排—— (A+10)*2 與 A*2+10 顯然不相等,但指令3可以重排到指令1、2之前或者中間,只要保證處理器執(zhí)行后面依賴到A、B值的操作時能獲取正確的A和B值即可。所以在同一個處理器中,重排序過的代碼看起來依然是有序的。因此,lock addl$0x0,(%esp) 指令把修改同步到內(nèi)存時,意味著所有之前的操作都已經(jīng)執(zhí)行完成,這樣便形成了“指令重排序無法越過內(nèi)存屏障”的效果。

假定T表示一個線程,V和W分別表示兩個volatile型變量,那么在進行read、load、use、assign、store和write操作時需要滿足如下對于volatile變量的特殊規(guī)則:

  • 只有當線程T對變量V執(zhí)行的前一個動作是load的時候,線程T才能對變量V執(zhí)行use動作;并且,只有當線程T對變量V執(zhí)行的后一個動作是use的時候,線程T才能對變量V執(zhí)行l(wèi)oad動作。線程T對變量V的use動作可以認為是和線程T對變量V的load、read動作相關(guān)聯(lián)的,必須連續(xù)且一起出現(xiàn)。(及時得到volatile變量的新值)
  • 只有當線程T對變量V執(zhí)行的前一個動作是assign的時候,線程T才能對變量V執(zhí)行store動作;并且,只有當線程T對變量V執(zhí)行的后一個動作是store的時候,線程T才能對變量V執(zhí)行assign動作。線程T對變量V的assign動作可以認為是和線程T對變量V的store、write動作相關(guān)聯(lián)的,必須連續(xù)且一起出現(xiàn)。(及時將volatile變量的改動同步到主存)
  • 假定動作A是線程T對變量V實施的use或assign動作,假定動作F是和動作A相關(guān)聯(lián)的load或store動作,假定動作P是和動作F相應(yīng)的對變量V的read或write動作;與此類似,假定動作B是線程T對變量W實施的use或assign動作,假定動作G是和動作B相關(guān)聯(lián)的load或store動作,假定動作Q是和動作G相應(yīng)的對變量W的read或write動作。如果A先于B,那么P先于Q。(禁止指令重排序優(yōu)化)

3. 關(guān)于內(nèi)存屏障

內(nèi)存屏障又稱內(nèi)存柵欄(Memory Barrier)是一個CPU指令,它的作用有兩個:

  • 保證特定操作的執(zhí)行順序
  • 保證某些變量的內(nèi)存可見性(利用該特性實現(xiàn)volatile的內(nèi)存可見性)

由于編譯器和處理器都能執(zhí)行指令重排優(yōu)化。如果在指令間插入一條Memory Barrier則會告訴編譯器和CPU,不管什么指令都不能和這條Memory Barrier指令重排序,也就是說通過插入內(nèi)存屏障禁止在內(nèi)存屏障前后的指令執(zhí)行重排序優(yōu)化。Memory Barrier的另外一個作用是強制刷出各種CPU的緩存數(shù)據(jù),因此任何CPU。上的線程都能讀取到這些數(shù)據(jù)的最新版本??傊畍olatile變量正是通過內(nèi)存屏障實現(xiàn)其在內(nèi)存中的語義,即可見性和禁止重排優(yōu)化。

Intel硬件提供了一系列的內(nèi)存屏障, 主要有:

  • Ifence,是一種Load Barrier讀屏障
  • sfence,是一種Store Barrier寫屏障
  • mfence,是一種全能型的屏障, 具備ifence和sfence的能力
  • Lock前綴, Lock不是一種內(nèi)存屏障,但是它能完成類似內(nèi)存屏障的功能。Lock會對CPU總線和高速緩存加鎖,可以理解為CPU指令級的一種鎖。它后面可以跟ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG, CMPXCH8B, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, and XCHG等指令。

不同硬件實現(xiàn)內(nèi)存屏障的方式不同,Java內(nèi)存模型屏蔽了這種底層硬件平臺的差異,由JVM來為不同的平臺生成相應(yīng)的機器碼。

JVM中提供了四類內(nèi)存屏障指令:

volatile內(nèi)存語義的實現(xiàn):

總的來說:

  • 當?shù)谝粋€操作是volatile讀時,不管第二個操作是什么,都不能重排序。這個規(guī)則確保volatile讀之后的操作不會被編譯器重排序到volatile讀之前。
  • 當?shù)诙€操作是volatile寫時,不管第一個操作是什么, 都不能重排序。這個規(guī)則確保volatile寫之前的操作不會被編譯器重排序到volatile寫之后。
  • 當?shù)谝粋€操作是volatile寫,第二個操作是volatile讀或?qū)憰r,不能重排序。

為了實現(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)存語義。

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

上圖中StoreLoad屏障的作用是避免volatile寫與后面可能有的 volatile讀/寫 操作重排序。因為編譯器常常無法準確判斷在一個volatile寫的后面 否需要插入一個StoreLoad屏障(比如,一個volatile寫之后方法立即return)。為了保證能正確實現(xiàn)volatile的內(nèi)存語義,JMM在采取了保守策略:在每個volatile寫的后面,或者在每個volatile讀的前面插入一個StoreLoad屏障。

從整體執(zhí)行效率的角度考慮,JMM最終選擇了在每個volatile寫的后面插入一個StoreLoad屏障。因為volatile寫-讀內(nèi)存語義的常見使用模式是:一個寫線程寫volatile變量,多個讀線程讀同一個volatile變量。 當讀線程的數(shù)量大大超過寫線程時,選擇在volatile寫之后插入StoreLoad屏障將帶來可觀的執(zhí)行效率的提升。從這里可以看到JMM在實現(xiàn)上的一個特點:首先確保正確性,然后再去追求執(zhí)行效率。

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

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

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

注意,最后的StoreLoad屏障不能省略。因為第二個volatile寫之后, 方法立即return。 此時編譯器可能無法準確斷定后面是否會有volatile讀或?qū)?,為了安全起見編譯器通常會在這里插入一個StoreLoad屏障。上面的優(yōu)化針對任意處理器平臺,由于不同的處理器有不同“松緊度"的處理器內(nèi)存模型,內(nèi)存屏障的插入還可以根據(jù)具體的處理器內(nèi)存模型繼續(xù)優(yōu)化。

4. 原子性、可見性與有序性

保證原子性(Atomicity):

  • JMM定義的原子操作:JMM來直接保證的原子性變量的操作包括read、load、assign、use、store和write這六個。
  • synchronized關(guān)鍵字:Java內(nèi)存模型還提供了lock和unlock操作來滿足更大范圍的原子性保證,盡管虛擬機未把lock和unlock操作直接開放給用戶使用,但是卻提供了更高層次的字節(jié)碼指令monitorenter和monitorexit來隱式地使用這兩個操作。反映到Java代碼中就是同步塊synchronized關(guān)鍵字,因此在synchronized塊之間的操作也具備原子性。

保證可見性(Visibility):

  • volatile關(guān)鍵字:JMM對于volatile定義的特殊規(guī)則保證了新值能立即同步到主內(nèi)存,以及每次使用前立即從主內(nèi)存刷新來保證可見性的。
  • synchronized關(guān)鍵字:同步塊的可見性是由“對一個變量執(zhí)行unlock操作之前,必須先把此變量同步回主內(nèi)存中(執(zhí)行store、write操作)”這條規(guī)則獲得的。
  • final關(guān)鍵字:被final修飾的字段在構(gòu)造器中一旦被初始化完成,并且構(gòu)造器沒有把“this”的引用傳遞出去(this引用逃逸是一件很危險的事情,其他線程有可能通過這個引用訪問到“初始化了一半”的對象),那么在其他線程中就能看見final字段的值。

保證有序性(Ordering):

  • As-If-Serial:如果在本線程內(nèi)觀察,所有的操作都是有序的。
  • volatile關(guān)鍵字:volatile關(guān)鍵字本身就包含了禁止指令重排序的語義,在具體的實現(xiàn)中(依賴內(nèi)存屏障)就會保證指令的有序性。
  • synchronized關(guān)鍵字:“一個變量在同一個時刻只允許一條線程對其進行l(wèi)ock操作”這條規(guī)則決定了持有同一個鎖的兩個同步塊只能串行地進入而保證執(zhí)行時的有序性。

5. Happens-Before

先行發(fā)生是(Happens-Before) Java內(nèi)存模型中定義的兩項操作之間的偏序關(guān)系,比如說操作A先行發(fā)生于操作B,其實就是說在發(fā)生操作B之前,操作A產(chǎn)生的影響能被操作B觀察到,“影響”包括修改了內(nèi)存中共享變量的值、發(fā)送了消息、調(diào)用了方法等。(先行發(fā)生原則是JMM的實現(xiàn)所體現(xiàn)出的一些特定的現(xiàn)象)Java語言無須任何同步手段保障就能成立的先行發(fā)生規(guī)則有且只有下面這些:

  • 程序次序規(guī)則(Program Order Rule):在一個線程內(nèi),按照控制流順序,書寫在前面的操作先行發(fā)生于書寫在后面的操作。注意,這里說的是控制流順序而不是程序代碼順序,因為要考慮分支、循環(huán)等結(jié)構(gòu)。
  • 管程鎖定規(guī)則(Monitor Lock Rule):一個unlock操作先行發(fā)生于后面對同一個鎖的lock操作。這里必須強調(diào)的是“同一個鎖”,而“后面”是指時間上的先后。
  • volatile變量規(guī)則(Volatile Variable Rule):對一個volatile變量的寫操作先行發(fā)生于后面對這個變量的讀操作,這里的“后面”同樣是指時間上的先后。
  • 線程啟動規(guī)則(Thread Start Rule):Thread對象的start()方法先行發(fā)生于此線程的每一個動作。
  • 線程終止規(guī)則(Thread Termination Rule):線程中的所有操作都先行發(fā)生于對此線程的終止檢測,我們可以通過Thread::join()方法是否結(jié)束、Thread::isAlive()的返回值等手段檢測線程是否已經(jīng)終止執(zhí)行。
  • 線程中斷規(guī)則(Thread Interruption Rule):對線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測到中斷事件的發(fā)生,可以通過Thread::interrupted()方法檢測到是否有中斷發(fā)生。
  • 對象終結(jié)規(guī)則(Finalizer Rule):一個對象的初始化完成(構(gòu)造函數(shù)執(zhí)行結(jié)束)先行發(fā)生于它的finalize()方法的開始。
  • 傳遞性(Transitivity):如果操作A先行發(fā)生于操作B,操作B先行發(fā)生于操作C,那就可以得出操作A先行發(fā)生于操作C的結(jié)論。

舉個栗子:

private int value = 0;

pubilc void setValue(int value){
    this.value = value;
}
public int getValue(){
    return value;
}

假設(shè)存在線程A和B,線程A先(時間上的先后)調(diào)用了setValue(1),然后線程B調(diào)用了同一個對象的getValue(),那么線程B收到的返回值是什么?

根據(jù)先行發(fā)生原則中的各項規(guī)則來進行判斷:

  • 由于兩個方法分別由線程A和B調(diào)用,不在一個線程中,所以程序次序規(guī)則在這里不適用;
  • 由于沒有同步塊,自然就不會發(fā)生lock和unlock操作,所以管程鎖定規(guī)則不適用;
  • 由于value變量沒有被volatile關(guān)鍵字修飾,所以volatile變量規(guī)則不適用;
  • 后面的線程啟動、終止、中斷規(guī)則和對象終結(jié)規(guī)則也和這里完全沒有關(guān)系;
  • 因為沒有一個適用的先行發(fā)生規(guī)則,所以最后一條傳遞性也無法滿足;

因此我們可以判定,盡管線程A在操作時間上先于線程B,但是無法確定線程B中g(shù)etValue()方法的返回結(jié)果,換句話說,這里面的操作不是線程安全的。

再舉個栗子:

// 以下操作在同一個線程中執(zhí)行
int i = 1;
int j = 2;

根據(jù)程序次序規(guī)則,“int i=1”的操作先行發(fā)生于“int j=2”,但是“int j=2”的代碼完全可能先被處理器執(zhí)行,這并不影響先行發(fā)生原則的正確性,因為我們在這條線程之中沒有辦法感知到這一點。

到此這篇關(guān)于必須要學會的JMM與volatile的文章就介紹到這了,更多相關(guān)JMM與volatile內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論