關(guān)于Java關(guān)鍵字volatile的總結(jié)-創(chuàng)新互聯(lián)

1 什么是 volatile

volatile 是 Java 的一個(gè)關(guān)鍵字,它提供了一種輕量級(jí)的同步機(jī)制。相比于重量級(jí)鎖 synchronized,volatile 更為輕量級(jí),因?yàn)樗粫?huì)引起線程上下文的切換和調(diào)度。

創(chuàng)新互聯(lián)公司是一家專(zhuān)注于成都網(wǎng)站制作、網(wǎng)站建設(shè)、外貿(mào)網(wǎng)站建設(shè)與策劃設(shè)計(jì),臨夏網(wǎng)站建設(shè)哪家好?創(chuàng)新互聯(lián)公司做網(wǎng)站,專(zhuān)注于網(wǎng)站建設(shè)10多年,網(wǎng)設(shè)計(jì)領(lǐng)域的專(zhuān)業(yè)建站公司;建站業(yè)務(wù)涵蓋:臨夏等地區(qū)。臨夏做網(wǎng)站價(jià)格咨詢(xún):13518219792

2 volatile 的兩個(gè)作用

  1. 可以禁止指令的重排序優(yōu)化
  2. 提供多線程訪問(wèn)共享變量的內(nèi)存可見(jiàn)性

3 禁止指令重排

3.1 什么是指令重排

指令重排序是 JVM 為了優(yōu)化指令,提高程序運(yùn)行效率,在不影響單線程程序執(zhí)行結(jié)果的前提下,盡可能地提高并行度,例如將多條指令并行執(zhí)行或者是調(diào)整指令的執(zhí)行順序。但是在多線程的情況下,指令重排序可能會(huì)帶來(lái)問(wèn)題,例如程序執(zhí)行的順序可能會(huì)被調(diào)整。在加上 volatile 關(guān)鍵字之后可以有效解決這個(gè)問(wèn)題。

下面我們舉個(gè)例子:

double r = 2.1; //(1) 
double pi = 3.14;//(2) 
double area = pi*r*r;//(3)

在代碼語(yǔ)句的順序?yàn)?1->2->3,但實(shí)際上順序無(wú)論是 1->2->3 還是 2->1->3 對(duì)結(jié)果并無(wú)影響,所以在編譯時(shí)和運(yùn)行時(shí)可以根據(jù)需要對(duì)1、2語(yǔ)句進(jìn)行重排序。

重排序是指編譯器和處理器為了優(yōu)化程序性能而對(duì)指令序列進(jìn)行排序的一種手段。重排序需要遵守一定規(guī)則:
1 不會(huì)對(duì)存在數(shù)據(jù)依賴(lài)關(guān)系的操作進(jìn)行重排序
2 重排序是為了優(yōu)化性能,但是不管怎么重排序,單線程下程序的執(zhí)行結(jié)果不能被改變

3.2 指令重排帶來(lái)的問(wèn)題

我們來(lái)看看這個(gè)基于雙重檢驗(yàn)的單例模式:

public class Singleton3 {
    private static Singleton3 instance = null;

    private Singleton3() {}

    public static Singleton3 getInstance() {
        if (instance == null) {
            synchronized(Singleton3.class) {
                if (instance == null)
                    instance = new Singleton3();// 非原子操作
            }
        }

        return instance;
    }
}

事實(shí)上,這個(gè)單例模式的實(shí)現(xiàn)方式是有問(wèn)題的,問(wèn)題在哪呢?問(wèn)題在于instance = new Singleton3();并不是一個(gè)原子操作。

我們可以將其抽象成以下幾條指令:

memory =allocate();     //1:分配對(duì)象的內(nèi)存空間 
ctorInstance(memory);   //2:初始化對(duì)象 
instance =memory;       //3:設(shè)置instance指向剛分配的內(nèi)存地址

可以看到,操作2依賴(lài)于操作1,但操作3并不依賴(lài)于操作2。所以 JVM 是可以針對(duì)它們進(jìn)行指令的優(yōu)化重排序的,經(jīng)過(guò)重排序后如下:

memory =allocate();    //1:分配對(duì)象的內(nèi)存空間 
instance =memory;      //3:instance指向剛分配的內(nèi)存地址,此時(shí)對(duì)象還未初始化
ctorInstance(memory);  //2:初始化對(duì)象

指令重排之后,instance 指向分配好的內(nèi)存放在了前面,而這段內(nèi)存的初始化被排在了后面。在線程A執(zhí)行這段賦值語(yǔ)句,在初始化分配對(duì)象之前就已經(jīng)將其賦值給 instance 引用,恰好另一個(gè)線程進(jìn)入方法判斷 instance 引用不為 null,然后就將其返回使用,導(dǎo)致出錯(cuò)。

3.3 禁止指令重排的原理

volatile 關(guān)鍵字提供內(nèi)存屏障的方式來(lái)防止指令被重排,編譯器在生成字節(jié)碼文件時(shí),會(huì)在指令序列中插入內(nèi)存屏障來(lái)禁止特定類(lèi)型的處理器重排序。

內(nèi)存屏障會(huì)確保指令重排序時(shí)不會(huì)把其后面的指令排到內(nèi)存屏障之前的位置,也不會(huì)把前面的指令排到內(nèi)存屏障的后面;即在執(zhí)行到內(nèi)存屏障這句指令時(shí),在它前面的操作已經(jīng)全部完成。

對(duì)于上面的基于雙重檢驗(yàn)的單例模式,我們只需對(duì)其稍作修改即可令其正確運(yùn)行。我們已經(jīng)知道,問(wèn)題來(lái)自于指令重排,那么我們禁止指令重排即可,用 volatile 關(guān)鍵字修飾 instance 變量,使得 instance 在讀、寫(xiě)操作前后都會(huì)插入內(nèi)存屏障,避免重排序。完整代碼如下:

public class Singleton3 {
    private static volatile Singleton3 instance = null;

    private Singleton3() {}

    public static Singleton3 getInstance() {
        if (instance == null) {
            synchronized(Singleton3.class) {
                if (instance == null)
                    instance = new Singleton3();
            }
        }
        return instance;
    }
}

4 保證內(nèi)存可見(jiàn)性

4.1 什么是保證內(nèi)存可見(jiàn)性

Java 支持多個(gè)線程同時(shí)訪問(wèn)一個(gè)對(duì)象或者對(duì)象的成員變量,由于每個(gè)線程可以擁有這個(gè)變量的拷貝(雖然對(duì)象以及成員變量分配的內(nèi)存是在共享內(nèi)存中的,但是每個(gè)執(zhí)行的線程還是可以擁有一份拷貝,這樣做的目的是加速程序的執(zhí)行,這是現(xiàn)代多核處理器的一個(gè)顯著特性),所以程序在執(zhí)行過(guò)程中,一個(gè)線程看到的變量并不一定是最新的。volatile 告知程序任何對(duì)該變量的訪問(wèn)均需要從共享內(nèi)存中獲取,而對(duì)它的改變必須同步刷新回共享內(nèi)存,它能保證所有線程對(duì)變量訪問(wèn)的可見(jiàn)性。

4.2 實(shí)現(xiàn)的具體細(xì)節(jié)

如果對(duì)聲明了 volatile 的變量進(jìn)行寫(xiě)操作,JVM 就會(huì)向處理器發(fā)送一條 Lock 前綴的指令,將這個(gè)變量所在緩存行的數(shù)據(jù)寫(xiě)回到系統(tǒng)內(nèi)存。

但是,就算寫(xiě)回到內(nèi)存,如果其他處理器緩存的值還是舊的,再執(zhí)行計(jì)算操作就會(huì)有問(wèn)題。所以,在多處理器下,為了保證各個(gè)處理器的緩存是一致的,就會(huì)實(shí)現(xiàn)緩存一致性協(xié)議,每個(gè)處理器通過(guò)嗅探在總線上傳播的數(shù)據(jù)來(lái)檢查自己緩存的值是不是過(guò)期了,當(dāng)處理器發(fā)現(xiàn)自己緩存行對(duì)應(yīng)的內(nèi)存地址被修改,就會(huì)將當(dāng)前處理器的緩存行設(shè)置成無(wú)效狀態(tài),當(dāng)處理器對(duì)這個(gè)數(shù)據(jù)進(jìn)行修改操作的時(shí)候,會(huì)重新從系統(tǒng)內(nèi)存中把數(shù)據(jù)讀到處理器緩存里。

具體的說(shuō),內(nèi)存可見(jiàn)性也是通過(guò)內(nèi)存屏障實(shí)現(xiàn)的,它會(huì)執(zhí)行下面兩個(gè)操作:

  1. 強(qiáng)制將對(duì)緩存的修改操作立即寫(xiě)入主存
  2. 如果是寫(xiě)操作,它會(huì)導(dǎo)致其他 CPU 中對(duì)應(yīng)的緩存行無(wú)效

5 總結(jié)

  1. volatile 提供了一種輕量級(jí)的同步機(jī)制,在訪問(wèn) volatile 變量時(shí)不會(huì)執(zhí)行加鎖操作,因此也就不會(huì)使執(zhí)行線程阻塞
  2. volatile 只能確??梢?jiàn)性,而加鎖機(jī)制既可以確保可見(jiàn)性又可以確保原子性
  3. volatile 屏蔽掉了 JVM 中必要的代碼優(yōu)化,所以在效率上比較低
  4. 相比 synchronized,雖然 volatile 更簡(jiǎn)單并且開(kāi)銷(xiāo)更低,但它的同步性較差,而且其使用也更容易出錯(cuò)

創(chuàng)新互聯(lián)www.cdcxhl.cn,專(zhuān)業(yè)提供香港、美國(guó)云服務(wù)器,動(dòng)態(tài)BGP最優(yōu)骨干路由自動(dòng)選擇,持續(xù)穩(wěn)定高效的網(wǎng)絡(luò)助力業(yè)務(wù)部署。公司持有工信部辦法的idc、isp許可證, 機(jī)房獨(dú)有T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確進(jìn)行流量調(diào)度,確保服務(wù)器高可用性。佳節(jié)活動(dòng)現(xiàn)已開(kāi)啟,新人活動(dòng)云服務(wù)器買(mǎi)多久送多久。

新聞標(biāo)題:關(guān)于Java關(guān)鍵字volatile的總結(jié)-創(chuàng)新互聯(lián)
瀏覽地址:http://muchs.cn/article16/dhgcgg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供微信小程序動(dòng)態(tài)網(wǎng)站、搜索引擎優(yōu)化、服務(wù)器托管企業(yè)網(wǎng)站制作、網(wǎng)站策劃

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

成都seo排名網(wǎng)站優(yōu)化