溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

Java中Volatile變量有什么用

發(fā)布時(shí)間:2021-10-29 13:06:44 來(lái)源:億速云 閱讀:104 作者:小新 欄目:開(kāi)發(fā)技術(shù)

這篇文章將為大家詳細(xì)講解有關(guān)Java中Volatile變量有什么用,小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。

    Volatile關(guān)鍵字是Java提供的一種輕量級(jí)的同步機(jī)制。Java 語(yǔ)言包含兩種內(nèi)在的同步機(jī)制:同步塊(或方法)和 volatile 變量, 相比synchronized(synchronized通常稱(chēng)為重量級(jí)鎖),volatile更輕量級(jí),因?yàn)樗粫?huì)引起線程上下文的切換和調(diào)度。 但是volatile 變量的同步性較差(有時(shí)它更簡(jiǎn)單并且開(kāi)銷(xiāo)更低),而且其使用也更容易出錯(cuò)。

    一、volatile變量的特性

    1.1、保證可見(jiàn)性,不保證原子性

    • 當(dāng)寫(xiě)一個(gè)volatile變量時(shí),JMM會(huì)把該線程本地內(nèi)存中的變量強(qiáng)制刷新到主內(nèi)存中去;

    • 這個(gè)寫(xiě)會(huì)操作會(huì)導(dǎo)致其他線程中的volatile變量緩存無(wú)效。

    來(lái)看一段代碼:

    public class Test {
        public static void main(String[] args) {
            WangZai wangZai = new WangZai();
            wangZai.start();
            for(; ;){
                if(wangZai.isFlag()){
                    System.out.println("hello");
                }
            }
        }
     
        static class WangZai extends Thread {
     
            private boolean flag = false;
     
            public boolean isFlag(){
                return flag;
            }
     
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                flag = true;
                System.out.println("flag = " + flag);
            }
        }
    }

    你會(huì)發(fā)現(xiàn),永遠(yuǎn)都不會(huì)輸出hello這一段代碼,按道理線程改了flag變量,主線程也能訪問(wèn)到的呀?

    但是將flag變量用volatile修飾一下,就能輸出hello這段代碼

    private volatile boolean flag = false;

    每個(gè)線程操作數(shù)據(jù)的時(shí)候會(huì)把數(shù)據(jù)從主內(nèi)存讀取到自己的工作內(nèi)存,如果他操作了數(shù)據(jù)并且寫(xiě)會(huì)了,那其他已經(jīng)讀取的線程的變量副本就會(huì)失效了,需要對(duì)數(shù)據(jù)進(jìn)行操作又要再次去主內(nèi)存中讀取了。

    volatile保證不同線程對(duì)共享變量操作的可見(jiàn)性,也就是說(shuō)一個(gè)線程修改了volatile修飾的變量,當(dāng)修改寫(xiě)回主內(nèi)存時(shí),另外一個(gè)線程立即看到最新的值。

    1.2、禁止指令重排

    重排序需要遵守一定規(guī)則:

    • 重排序操作不會(huì)對(duì)存在數(shù)據(jù)依賴(lài)關(guān)系的操作進(jìn)行重排序。

    • 重排序是為了優(yōu)化性能,但是不管怎么重排序,單線程下程序的執(zhí)行結(jié)果不能被改變。

    什么是重排序?

    為了提高性能,編譯器和處理器常常會(huì)對(duì)既定的代碼執(zhí)行順序進(jìn)行指令重排序。

    重排序的類(lèi)型有哪些呢?

    Java中Volatile變量有什么用

    一個(gè)好的內(nèi)存模型實(shí)際上會(huì)放松對(duì)處理器和編譯器規(guī)則的束縛,也就是說(shuō)軟件技術(shù)和硬件技術(shù)都為同一個(gè)目標(biāo),而進(jìn)行奮斗:在不改變程序執(zhí)行結(jié)果的前提下,盡可能提高執(zhí)行效率。

    JMM對(duì)底層盡量減少約束,使其能夠發(fā)揮自身優(yōu)勢(shì)。

    因此,在執(zhí)行程序時(shí),為了提高性能,編譯器和處理器常常會(huì)對(duì)指令進(jìn)行重排序。

    一般重排序可以分為如下三種:

    • 編譯器優(yōu)化的重排序。編譯器在不改變單線程程序語(yǔ)義的前提下,可以重新安排語(yǔ)句的執(zhí)行順序;

    • 指令級(jí)并行的重排序。現(xiàn)代處理器采用了指令級(jí)并行技術(shù)來(lái)將多條指令重疊執(zhí)行。如果不存在數(shù)據(jù)依賴(lài)性,處理器可以改變語(yǔ)句對(duì)應(yīng)機(jī)器指令的執(zhí)行順序;

    • 內(nèi)存系統(tǒng)的重排序。由于處理器使用緩存和讀/寫(xiě)緩沖區(qū),這使得加載和存儲(chǔ)操作看上去可能是在亂序執(zhí)行的。

    那 Volatile 是怎么保證不會(huì)被執(zhí)行重排序的呢?

    二、內(nèi)存屏障

    java編譯器會(huì)在生成指令系列時(shí)在適當(dāng)?shù)奈恢脮?huì)插入內(nèi)存屏障指令來(lái)禁止特定類(lèi)型的處理器重排序。

    為了實(shí)現(xiàn)volatile的內(nèi)存語(yǔ)義,JMM會(huì)限制特定類(lèi)型的編譯器和處理器重排序,JMM會(huì)針對(duì)編譯器制定volatile重排序規(guī)則表:

    是否能重排序第二個(gè)操作第一個(gè)操作普通讀/寫(xiě)volatile讀volatile寫(xiě)普通讀/寫(xiě)NOvolatile讀NONONOvolatile寫(xiě)NONO

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

    從上表我們可以看出:

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

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

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

    需要注意的是:volatile寫(xiě)是在前面和后面分別插入內(nèi)存屏障,而volatile讀操作是在后面插入兩個(gè)內(nèi)存屏障。

    寫(xiě)

    Java中Volatile變量有什么用

    Java中Volatile變量有什么用

    從JDK5開(kāi)始,提出了happens-before的概念,通過(guò)這個(gè)概念來(lái)闡述操作之間的內(nèi)存可見(jiàn)性。

    三、happens-before

    happens-before 關(guān)系的定義:

    • 如果一個(gè)操作 happens-before 另一個(gè)操作,那么第一個(gè)操作的執(zhí)行結(jié)果就會(huì)對(duì)第二個(gè)操作可見(jiàn)。

    • 兩個(gè)操作之間如果存在 happens-before 關(guān)系,并不意味著 Java 平臺(tái)的具體實(shí)現(xiàn)就必須按照 happens-before 關(guān)系指定的順序來(lái)執(zhí)行。如果重排序之后的執(zhí)行結(jié)果,與按照 happens-before 關(guān)系來(lái)執(zhí)行的結(jié)果一直,那么 JMM 也允許這樣的重排序。

    看到這兒,你是不是覺(jué)得,這個(gè)怎么和 as-if-serial 語(yǔ)義一樣呢。沒(méi)錯(cuò), happens-before 關(guān)系本質(zhì)上和 as-if-serial 語(yǔ)義是一回事。

    as-if-serial 語(yǔ)義保證的是單線程內(nèi)重排序之后的執(zhí)行結(jié)果和程序代碼本身應(yīng)該出現(xiàn)的結(jié)果是一致的,

    happens-before 關(guān)系保證的是正確同步的多線程程序的執(zhí)行結(jié)果不會(huì)被重排序改變。

    一句話(huà)來(lái)總結(jié)就是:如果操作 A happens-before 操作 B ,那么操作 A 在內(nèi)存上所做的操作對(duì)操作 B 都是可見(jiàn)的,不管它們?cè)诓辉谝粋€(gè)線程。

    在 Java 中,對(duì)于 happens-before 關(guān)系,有以下規(guī)定:

    • 程序順序規(guī)則:一個(gè)線程中的每一個(gè)操作, happens-before 于該線程中的任意后續(xù)操作。

    • 監(jiān)視器鎖規(guī)則:對(duì)一個(gè)鎖的解鎖, happens-before 于隨后對(duì)這個(gè)鎖的加鎖。

    • volatile 變量規(guī)則:對(duì)一個(gè) volatile 域的寫(xiě), 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() 操作成功返回。

    關(guān)于“Java中Volatile變量有什么用”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,使各位可以學(xué)到更多知識(shí),如果覺(jué)得文章不錯(cuò),請(qǐng)把它分享出去讓更多的人看到。

    向AI問(wèn)一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI