<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          JUC并發(fā)編程之Volatile關(guān)鍵字詳解

          共 16605字,需瀏覽 34分鐘

           ·

          2021-04-12 20:50

          1
          前言

          volatile關(guān)鍵字可以說是Java虛擬機提供的最輕量級的同步機制,但是它并不容易完全被正確、完整地理解,以至于許多程序員都習(xí)慣不去使用它,遇到需要處理多線程數(shù)據(jù)競爭問題的時候一律使用synchronized來進行同步。了解volatile變量的語義對了解多線程操作的其他特性很有意義,在本文中我們將介紹volatile的語義到底是什么。由于volatile關(guān)鍵字與Java內(nèi)存模型(Java Memory Model,JMM)有較多的關(guān)聯(lián),因此對于JMM內(nèi)存模型還不是很了解的,可以看我這篇文章 JUC并發(fā)編程之JMM內(nèi)存模型詳解


          1
          淺談volatile關(guān)鍵字


          2
          volatile內(nèi)存語義


          volatile是Java虛擬機提供的輕量級的同步機制。volatile關(guān)鍵字有如下兩個作用
          • 保證被volatile修飾的共享變量對所有線程總數(shù)可見的,也就是當(dāng)一個線程修改了一個被volatile修飾共享變量的值,新值總是可以被其他線程立即得知。

          • 禁止指令重排序優(yōu)化。


          2
          volatile特性分解


          本文圍繞著并發(fā)的三大特性(原子性、可見性、有序性)來聊一聊,volatile在并發(fā)中它能夠解決哪些問題


          3
          volatile可見性


          先上一段demo代碼,來看看加了volatile和沒加的區(qū)別。
          @Slf4jpublic class Test01 {    private static boolean initFlag = false;    public static void refresh() {        log.info("refresh data.......");        initFlag = true;        log.info("refresh data success.......");    }    public static void main(String[] args) {        Thread threadA = new Thread(() -> {            while (!initFlag) {            }            log.info("線程:" + Thread.currentThread().getName()                    + "當(dāng)前線程嗅探到initFlag的狀態(tài)的改變");        }, "threadA");        threadA.start();        try {            Thread.sleep(500);        } catch (InterruptedException e) {            e.printStackTrace();        }        Thread threadB = new Thread(() -> {            refresh();        }, "threadB");        threadB.start();    }}


          運行效果圖


          從動圖我們看到,A線程內(nèi)部判斷 "initFlag" 變量,如果變量的值為"false"則一直進行循環(huán),在代碼中B線程內(nèi)部調(diào)用refresh()方法將變量 "initFlag" 的值修改為"true",而此時A線程內(nèi)部的循環(huán)感應(yīng)到 "initFlag" 變量的值為"true"了應(yīng)該退出來才對,而為什么演示圖中A線程內(nèi)部的循環(huán)并沒有退出來?


          帶著這個疑惑,將代碼稍微改動一下,往 "initFlag" 變量加上"volatile",然后再來看看它的效果是如何?


          經(jīng)過兩輪的測試,從動圖中,我們可以很明顯的看到它們之間的區(qū)別,加了 "voaltile" 之后,A線程內(nèi)部循環(huán)的 "initFlag" 變量能夠感知到值發(fā)生的變化,然后跳出了循環(huán),這是為什么呢?


          先來看看這種圖,或許會更加的好理解一點


          對于上面的疑惑,我先做一個結(jié)論性的回答,然后再來分析它的過程,對于程序來說,其實我們無論是否加了 "volatile" A線程內(nèi)部的循環(huán)最終都會退出來,只不過加了"volatile"后,A線程能夠立馬感知到值發(fā)生的變化。


          分析結(jié)論:先看到我紅色標(biāo)記的一段話,A線程內(nèi)部的循環(huán)最終都會跳出來,只不過是時間長短的問題而已。


          結(jié)合上圖分析,initFlag作為成員變量,程序會將它存放在主內(nèi)存中,當(dāng)線程A和B啟動后,如果線程需要用到主內(nèi)存的initFlag,線程會從主內(nèi)存中將變量復(fù)制一份到自己內(nèi)部的工作內(nèi)存中,然后再對變量進行操作。而不是直接在線程內(nèi)部對主內(nèi)存中的變量進行操作。那么這就會有一個問題,當(dāng)線程B對工作內(nèi)存中的initFlag值進行改變后,然后將initFlag值從工作內(nèi)存中推回到主內(nèi)存,這時候線程A可能不會立即知道主內(nèi)存的值已經(jīng)發(fā)生了改變,因為A線程中的空循環(huán)它的優(yōu)先級是非常高的,它會一直占用CPU來執(zhí)行這串代碼,這就導(dǎo)致JVM無法讓CPU分點時間去主內(nèi)存中拉取最新的值。而加了volatile后,它會通知其他有用到initFlag變量的線程,強制它去拉取主內(nèi)存中最新變量的值,然后重新刷回到內(nèi)部的工作內(nèi)存中。簡單來說,加了volatile關(guān)鍵字會強制保證線程的可見性;而不加的話,JVM也會盡力的保證線程的可見性(也就是CPU空閑的時候),這也就是我前面為什么會說無論是否加了 "volatile" A線程內(nèi)部的循環(huán)最終都會退出來原因。


          看到這相信對volatile的可見性有了一定的了解,接著再繼續(xù)來看看volatile它是否能夠解決并發(fā)中的原子性呢?


          3
          volatile原子性


          老套路,先放上一段代碼demo
          public class Test02 {    private static int counter = 0;    public static void main(String[] args) {        for (int i = 0; i < 100; i++) {            Thread thread = new Thread(()->{                for (int j = 0; j < 1000; j++) {                        counter++;                }            });            thread.start();        }        try {            Thread.sleep(3000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(counter);    }}


          如下圖,是它的執(zhí)行結(jié)果


          這段代碼發(fā)起了100個線程,每個線程對counter變量進行1000次自增操作,如果這段代碼能夠正確并發(fā)的話,最后輸出的結(jié)果應(yīng)該是100000。運行完這段代碼之后,并不會獲得期望的結(jié)果,而且會發(fā)現(xiàn)每次運行程序,輸出的結(jié)果都不一樣,都是一個小于100000的數(shù)字,這是為什么?根據(jù)上面所說的,可能是成員變量沒有加volatile,然后導(dǎo)致每個線程內(nèi)的工作內(nèi)存的counter值沒有及時的得到更新,所以才導(dǎo)致的結(jié)果不對,那不妨我們再將volatile加到成員變量counter上,再來看看結(jié)果如何。


          下圖是在成員變量上加了volatile的效果圖


          嗯哼?好家伙,上圖打印的結(jié)果居然不是100000??看到是不是會感覺到有點不可思議?按照我文章中所說的volatile可見性的一個特性,當(dāng)我某一個線程修改了主內(nèi)存的值后,會立即通知其他的線程主動的去主內(nèi)存中拉取最新的值,這里應(yīng)該正常的輸出才對,難道我前面所說的結(jié)論不對嗎?其實并非不對,且看我細細道來。


          因為volatile它并不能夠解決并發(fā)中的原子性問題,看到這是不是又懵逼了?代碼中的counter++就一行代碼,為什么不是原子操作呢??其實這里是有一個坑的,其實counter++并非是一步操作,它在底層是被拆分為三個步驟進行執(zhí)行的,且看,counter++操作是counter = counter + 1的簡寫操作對吧,那么我們可以簡單的思考一下,counter的值是怎么來的呢?


          根據(jù)這個思考,再來拆分一下它三個細致的步驟:

          第一步:線程從主內(nèi)存中復(fù)制一份變量到內(nèi)部的工作內(nèi)存中(讀操作)
          第二步:對counter變量進行+1計算(計算操作)
          第三步:將計算后的值賦值給工作內(nèi)存的counter變量,然后推回到主內(nèi)存中(寫操作)


          我們都知道,線程是基于時間片進行執(zhí)行的,在多線程下,假如線程內(nèi)部剛好執(zhí)行完第一步或者第二步操作,這個時候CPU發(fā)生中斷操作,它并沒有去執(zhí)行該線程內(nèi)的第三步操作(意思是暫停執(zhí)行第三步操作,等到時間片輪詢到該線程再回來繼續(xù)執(zhí)行接下來的操作),轉(zhuǎn)而去執(zhí)行另外一個線程的一個自增操作,這個時候就會出現(xiàn)問題,第一個線程執(zhí)行完第二步操作后發(fā)生暫停,轉(zhuǎn)而執(zhí)行第二個線程自增操作,回看前面所說的volatile可見性特性, 因為加了volatile的原因,第二個線程改變完值后,會通知第一個線程現(xiàn)有的counter變量已經(jīng)過期,需要重新去拉取主內(nèi)存最新的值,這個時候就會造成,我兩個線程都發(fā)生了自增操作,但是只有一個線程自增成功了,那么結(jié)果自然就不對,這也就造成了線程安全的問題。


          從上面例子我們可以確定volatile是不能保證原子性的,要保證運算的原子性可以使用java.util.concurrent.atomic包下的一些原子操作類,或者使用synchronized同步塊和Lock鎖來解決該問題。


          3
          volatile有序性


          關(guān)于有序性,在程序中我們都知道,我們寫的代碼都是從上往下進行執(zhí)行的,那么在底層它是如何知道程序是從上往下的的呢?有沒有可能代碼會亂序執(zhí)行的呢?


          我前面有提到過線程是基于時間片執(zhí)行的,從時間的維度上來講,在線程內(nèi),上一行代碼總會比下一行代碼優(yōu)先執(zhí)行,但是在CPU里面它又不同了,它可能會將下一行的代碼放到上一行先去執(zhí)行,看到這估計有小伙伴有點懵了?啥玩意兒?這不是逗我玩嗎?代碼上中是從上往下執(zhí)行,結(jié)果到你CPU又給我亂序執(zhí)行?說著這,就不得不說到指令重排的概念了。


          什么是指令重排

          java語言規(guī)定JVM線程內(nèi)部維持順序語義,只要程序最終執(zhí)行結(jié)果與它順序化結(jié)果相等(一致的情況下),那么指令的執(zhí)行順序可以與代碼順序不一致,此過程叫指令重排。


          為什么要指令重排

          JVM能根據(jù)處理器特性(CPU多級緩存系統(tǒng)、多核處理器等)適當(dāng)?shù)膶C器指令進行重排序,使機器指令能更符合CPU的執(zhí)行特性,最大限度的發(fā)揮機器性能。
          上面這段話有點官方,我白話文來再來說一下,CPU在執(zhí)行你的代碼的時候,會認為你寫的代碼從上往下執(zhí)行的速度還沒有達到最優(yōu),它會在底層幫你優(yōu)化一下代碼的執(zhí)行順序,它是在不更改源結(jié)果的前提下進行優(yōu)化的。


          下圖為從源碼到最終執(zhí)行的指令序列示意圖:


          這里我來放上一段代碼,來證明一下它是否會進行指令重排
          @Slf4jpublic class Test03 {    private static int x = 0, y = 0;    private static int a = 0, b = 0;    public static void main(String[] args) throws InterruptedException {        int i = 0;        for (; ; ) {            i++;            x = 0;            y = 0;            a = 0;            b = 0;            Thread t1 = new Thread(()->{                shortWait(10000);                a = 1;                x = b;            });            Thread t2 = new Thread(()->{                b = 1;                y = a;            });            t1.start();            t2.start();            t1.join();            t2.join();            String result = "第" + i + "次 (" + x + "," + y + ")";            if (x == 0 && y == 0) {                System.out.println(result);                break;            } else {                log.info(result);            }        }    }    /**     * 等待一段時間,時間單位納秒     *     * @param interval     */    public static void shortWait(long interval) {        long start = System.nanoTime();        long end;        do {            end = System.nanoTime();        } while (start + interval >= end);    }}


          在運行代碼之前,先對這段代碼的變量"x""y"的打印的結(jié)果,做一個簡單的分析。看一下上述代碼中會出現(xiàn)多少種不同的結(jié)果。在下方我將通過時序圖來展現(xiàn)過程。


          第一種結(jié)果:先排除指令重排,當(dāng)這段代碼以我們視覺效果的從上往下執(zhí)行,結(jié)果就是x=0,y=1(因為t1線程已經(jīng)執(zhí)行完了,t2線程才來執(zhí)行)


          第二種結(jié)果:先排除指令重排,當(dāng)T2線程先執(zhí)行,然后在執(zhí)行T1線程,它的結(jié)果就是x=1,y=0


          第三種結(jié)果:先排除指令重排,當(dāng)T1線程先執(zhí)行,剛執(zhí)行第一步,發(fā)生CPU中斷操作,轉(zhuǎn)而執(zhí)行T2線程的第一步,結(jié)果又發(fā)生CPU中斷操作,CPU又回到T1線程繼續(xù)執(zhí)行第二步,最后又來執(zhí)行T2的第二步驟。所以最終的結(jié)果是x=1,y=1


          第四種結(jié)果:就是與第三種順序進行相反,不過并沒有意義,因為結(jié)果都是一樣的,這次我們不排除指令重排那么結(jié)果可能為:x=0,y=0


          看了上面四種分析,也不知道結(jié)果對錯與否,接下來貼出一張我測試的動態(tài)圖,來驗證指令重排的效果


          從動態(tài)圖中,是不是已經(jīng)可以驗證指令重排的存在了呢?那出現(xiàn)這種情況,有沒有辦法能夠禁止指令重排呢?當(dāng)然是可以的,volatile關(guān)鍵字完全可以解決這個問題


          如上圖中,我分別往x、y、a、b這變量加上了volatile,它就不會指令重排了,我動圖的效果比較時間比較短,不相信的話,大家伙可以自己測試一下。


          volatile禁止重排優(yōu)化

          volatile關(guān)鍵字另一個作用就是禁止指令重排優(yōu)化,從而避免多線程環(huán)境下程序出現(xiàn)亂序執(zhí)行的現(xiàn)象,關(guān)于指令重排優(yōu)化前面已詳細分析過,這里主要簡單說明一下volatile是如何實現(xiàn)禁止指令重排優(yōu)化的。先了解一個概念,內(nèi)存屏障(Memory Barrier)。


          內(nèi)存屏障其實簡單理解的話,假如代碼中有兩行代碼,這兩行代碼在底層可能會發(fā)生指令重排,那么我不想讓他發(fā)生重排怎么辦呢?內(nèi)存屏障的作用就體現(xiàn)出來啦,我們可以將內(nèi)存屏障插在兩行代碼中間,告訴編譯器或者CPU等,不讓它進行重排,當(dāng)然內(nèi)存屏障是關(guān)于硬件層面的一些知識了,其實JVM也幫我們基于硬件層面的內(nèi)存屏障封裝好了軟件層面的內(nèi)存屏障,先來看看硬件層的內(nèi)存屏障有哪些?


          硬件層的內(nèi)存屏障

          Intel硬件提供了一系列的內(nèi)存屏障,主要有: 
          1.lfence,是一種Load Barrier 讀屏障 
          2.sfence, 是一種Store Barrier 寫屏障 
          3.mfence, 是一種全能型的屏障,具備ifence和sfence的能力 
          4.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等指令。


          jvm層的內(nèi)存屏障
          不同硬件實現(xiàn)內(nèi)存屏障的方式不同,Java內(nèi)存模型屏蔽了這種底層硬件平臺的差異,由JVM來為不同的平臺生成相應(yīng)的機器碼。JVM中提供了四類內(nèi)存屏障指令:
          屏障類型
          指令示例
          說明
          LoadLoad
          Load1; LoadLoad; Load2
          保證load1的讀取操作在load2及后續(xù)讀取操作之前執(zhí)行
          StoreStore
          Store1; StoreStore; Store2
          在store2及其后的寫操作執(zhí)行前,保證store1的寫操作已刷新到主內(nèi)存
          LoadStore
          Load1; LoadStore; Store2
          在stroe2及其后的寫操作執(zhí)行前,保證load1的讀操作已讀取結(jié)束
          StoreLoad
          Store1; StoreLoad; Load2
          保證store1的寫操作已刷新到主內(nèi)存之后,load2及其后的讀操作才能執(zhí)行


          volatile內(nèi)存語義的實現(xiàn)
          下圖是JMM針對編譯器制定的volatile重排序規(guī)則表。
          第一個操作
          第二個操作:普通讀寫
          第二個操作:volatile讀
          第二個操作:volatile寫
          普通讀寫
          可以重排
          可以重排
          不可以重排
          volatile讀
          不可以重排
          不可以重排
          不可以重排
          volatile寫
          可以重排
          不可以重排
          不可以重排


          看到這,針對上面的幾個表格看的是不是還有點懵圈,沒關(guān)系,我接下來會對上面的表格的內(nèi)容做一個簡單總結(jié),以及通過代碼演示。相信大家伙應(yīng)該會收獲很多。


          總結(jié):普通讀寫和volatile讀寫的概念
          1.普通讀的概念:讀取的變量可以是局部變量或是成員變量,成員變量不能被volatile所修飾
          2.普通寫的概念:賦值的變量可以是局部變量或是成員變量,成員變量不能被volatile所修飾
          3.volatile讀概念:讀取的變量必須是被volatile所修飾的變量
          4.volatile寫概念:賦值的變量必須是被volatile所修飾的變量


          總結(jié):根據(jù)上方的表格的每一行操作,下方通過詳細的代碼Demo進行演示
          第一行demo演示
          public class ReadAndWrithe {    int a = 1;    int c = 0;    private static volatile int d = 5;    /**     * 第一個操作普通讀寫  第二個操作普通讀寫  可以重排     */    public void test1() {        //第一個操作:普通讀寫        //讀取的a變量是成員變量但是沒有被volatile所修飾,所以為普通讀操作        //定義的b變量是局部變量,所以為普通寫操作        int b = a + 1;        //第二個操作:普通讀寫        //讀取的a變量和b變量都沒有被volatile所修飾,所以為普通讀操作        //定義的c變量是成員變量沒有被volatile所修飾,所以為普通寫操作        c = 2;        //該結(jié)論則是可以重排    }    /**     * 第一個操作普通讀寫  第二個操作volatile讀  可以重排     */    public void test2() {        //第一個操作:普通讀寫        //讀取的a變量是成員變量但是沒有被volatile所修飾,所以為普通讀操作        //定義的b變量是局部變量,所以為普通寫操作        int b = a + 1;        //第二個操作:volatile讀(準確來說:volatile讀,普通寫)        //讀取的d變量是成員變量且是被volatile所修飾,所以為volatile讀操作        //定義的c變量是成員變量沒有被volatile所修飾,所以為普通寫操作        c = d;        //該結(jié)論則是可以重排    }    /**     * 第一個操作普通讀  第二個操作volatile寫  不可以重排     */    public void test3() {        //第一個操作:普通讀寫        //讀取的a變量是成員變量但是沒有被volatile所修飾,所以為普通讀操作        //定義的b變量是局部變量,所以為普通寫操作        int b = a + 1;        //第二個操作:volatile寫(準確來說:volatile寫,普通讀)        //讀取的c變量是成員變量但是沒有被volatile所修飾,所以為普通讀操作        //賦值d變量是成員變量且是被volatile所修飾,所以為volatile寫操作        d = c;        //該結(jié)論則是不可以重排    }}


          第二行demo演示
          public class ReadAndWrithe {    int a = 1;    int c = 0;    private static volatile int d = 5;    /**     * 第一個操作為volatile讀,第二個操作為普通讀寫  不允許重排     */    public void test1() {        //第一個操作:volatile讀(準確來說:volatile讀,普通寫)        //讀取的d變量是成員變量是被volatile所修飾,所以為volatile讀        //定義的j變量為成員變量,所以為普通寫        int j = d;        //第二個操作:普通讀寫        a = 5;    }    /**     * 第一個操作為volatile讀,第二個操作為volatile讀  不允許重排     */    public void test2() {        //第一個操作:volatile讀        //讀取的d變量是成員變量是被volatile所修飾,所以為volatile讀        //定義的j變量為成員變量,所以為普通寫        int j = d;        //第二個操作:volatile讀        //讀取的d變量是成員變量是被volatile所修飾,所以為volatile讀        //定義的f變量為成員變量,所以為普通寫        int f = d;    }    /**     * 第一個操作為volatile讀,第二個操作為volatile寫  不允許重排     */    public void test3() {        //第一個操作:volatile讀        //讀取的d變量是成員變量是被volatile所修飾,所以為volatile讀        //定義的j變量為成員變量,所以為普通寫        int j = d;        //第二個操作:volatile寫        //讀取的a變量是成員變量但不是被volatile所修飾,普通讀        //賦值的d變量為volatile所修飾,所以為volatile寫        d = a;    }}


          第三行demo演示
          public class ReadAndWrithe {    int a = 1;    int c = 0;    private static volatile int d = 5;    private static volatile int d2 = 2;    /**     * 第一個操作為volatile寫,第二個操作為普通讀寫  可以重排     */    public void test1() {        //第一個操作:volatile寫(準確來說:volatile寫,普通讀)        //3:普通讀        //賦值的d是volatile所修飾的,所以為volatile寫        d = 3;            //第二個操作:普通讀寫        a = 5;    }    /**     * 第一個操作為volatile寫,第二個操作為volatile讀  不允許重排     */    public void test2() {        //第一個操作:volatile寫        //3:普通讀        //賦值的d是volatile所修飾的,所以為volatile寫        d = 3;        //第二個操作:volatile讀        //讀取的d變量是成員變量是被volatile所修飾,所以為volatile讀        //定義的f變量為成員變量,所以為普通寫        int f = d;    }    /**     * 第一個操作為volatile寫,第二個操作為volatile寫  不允許重排     */    public void test3() {        //第一個操作:volatile寫        //3:普通讀        //賦值的d是volatile所修飾的,所以為volatile寫        d = 3;        //第二個操作:volatile寫        //讀取的a變量是成員變量但不是被volatile所修飾,普通讀        //賦值的d2變量為volatile所修飾,所以為volatile寫        d2 = a;    }}


          指令重排造成的問題
          例如單例模式-雙重檢驗鎖創(chuàng)建實例,在多并發(fā)情況下則會出現(xiàn)問題,這個會在后面單獨出一篇文章來剖析它,為什么會出現(xiàn)問題,本文先在這里埋上一個坑~


          當(dāng)然jvm虛擬機也不會隨意將我們的代碼進行指令重排,還需要遵守以下規(guī)則

          as-if-serial語義
          as-if-serial語義的意思是:不管怎么重排序(編譯器和處理器為了提高并行度),(單線程)程序的執(zhí)行結(jié)果不能被改變。編譯器、runtime和處理器都必須遵守as-if-serial語義。
          為了遵守as-if-serial語義,編譯器和處理器不會對存在數(shù)據(jù)依賴關(guān)系的操作做重排序,因為這種重排序會改變執(zhí)行結(jié)果。但是,如果操作之間不存在數(shù)據(jù)依賴關(guān)系,這些操作就可能被編譯器和處理器重排序。


          happens-before 原則

          只靠sychronized和volatile關(guān)鍵字來保證原子性、可見性以及有序性,那么編寫并發(fā)程序可能會顯得十分麻煩,幸運的是,從JDK 5開始,Java使用新的JSR-133內(nèi)存模型,提供了happens-before 原則來輔助保證程序執(zhí)行的原子性、可見性以及有序性的問題,它是判斷數(shù)據(jù)是否存在競爭、線程是否安全的依據(jù),happens-before 原則內(nèi)容如下

          1.程序順序原則,即在一個線程內(nèi)必須保證語義串行性,也就是說按照代碼順序執(zhí)行。

          2.鎖規(guī)則 解鎖(unlock)操作必然發(fā)生在后續(xù)的同一個鎖的加鎖(lock)之前,也就是說,如果對于一個鎖解鎖后,再加鎖,那么加鎖的動作必須在解鎖動作之后(同一個鎖)。

          3.volatile規(guī)則 volatile變量的寫,先發(fā)生于讀,這保證了volatile變量的可見性,簡單的理解就是,volatile變量在每次被線程訪問時,都強迫從主內(nèi)存中讀該變量的值,而當(dāng)該變量發(fā)生變化時,又會強迫將最新的值刷新到主內(nèi)存,任何時刻,不同的線程總是能夠看到該變量的最新值。

          4.線程啟動規(guī)則 線程的start()方法先于它的每一個動作,即如果線程A在執(zhí)行線程B的start方法之前修改了共享變量的值,那么當(dāng)線程B執(zhí)行start方法時,線程A對共享變量的修改對線程B可見

          5.傳遞性 A先于B ,B先于C 那么A必然先于C

          6.線程終止規(guī)則 線程的所有操作先于線程的終結(jié),Thread.join()方法的作用是等待當(dāng)前執(zhí)行的線程終止。假設(shè)在線程B終止之前,修改了共享變量,線程A從線程B的join方法成功返回后,線程B對共享變量的修改將對線程A可見。

          7.線程中斷規(guī)則 對線程 interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測到中斷事件的發(fā)生,可以通過Thread.interrupted()方法檢測線程是否中斷。

          8.對象終結(jié)規(guī)則對象的構(gòu)造函數(shù)執(zhí)行,結(jié)束先于finalize()方法


          我是黎明大大,我知道我沒有驚世的才華,也沒有超于凡人的能力,但畢竟我還有一個不屈服,敢于選擇向命運沖鋒的靈魂,和一個就是傷痕累累也要義無反顧走下去的心。


          如果您覺得本文對您有幫助,還請關(guān)注點贊一波,后期將不間斷更新更多技術(shù)文章


          掃描二維碼關(guān)注我
          不定期更新技術(shù)文章哦



          JUC并發(fā)編程之JMM內(nèi)存模型詳解

          深入Hotspot源碼與Linux內(nèi)核理解NIO與Epoll

          基于Python爬蟲爬取有道翻譯實現(xiàn)翻譯功能

          JAVA集合之ArrayList源碼分析

          Mysql幾種join連接算法



          發(fā)現(xiàn)“在看”和“贊”了嗎,因為你的點贊,讓我元氣滿滿哦
          瀏覽 48
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产黄色片在线 | 欧美后门菊门交3p视频 | 亚洲AV无码乱码国产精品 | 蜜桃四季春秘 一区二区三区 | 奇米影视狠狠干 |