成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

java volatile 關(guān)鍵字

scola666 / 615人閱讀

摘要:舉個(gè)例子,在多線程不使用環(huán)境中,每個(gè)線程會(huì)從主存中復(fù)制變量到緩存以提高性能。保證了變量的可見(jiàn)性關(guān)鍵字解決了變量的可見(jiàn)性問(wèn)題。在多線程同時(shí)共享變量的情形下,關(guān)鍵字已不足以保證程序的并發(fā)性。

volatile 關(guān)鍵字能把 Java 變量標(biāo)記成"被存儲(chǔ)到主存中"。這表示每一次讀取 volatile 變量都會(huì)訪問(wèn)計(jì)算機(jī)主存,而不是 CPU 緩存。每一次對(duì) volatile 變量的寫操作不僅會(huì)寫到 CPU 緩存,還會(huì)刷新到主存中。
實(shí)際上從 Java 5 開(kāi)始,volatile 變量不僅會(huì)在讀寫操作時(shí)訪問(wèn)主存,他還被賦予了更多含義。

變量的可見(jiàn)性問(wèn)題

Java volatile 關(guān)鍵字保證了線程對(duì)變量改動(dòng)的可見(jiàn)性。
舉個(gè)例子,在多線程 (不使用 volatile) 環(huán)境中,每個(gè)線程會(huì)從主存中復(fù)制變量到 CPU 緩存 (以提高性能)。如果你有多個(gè) CPU,不同線程也許會(huì)運(yùn)行在不同的 CPU 上,并把主存中的變量復(fù)制到各自的 CPU 緩存中,像下圖畫的那樣

若果不使用 volatile 關(guān)鍵字,你無(wú)法保證 JVM 什么時(shí)候從主存中讀變量到 CPU cache,或把變量從 CPU cache 寫回主存。這會(huì)導(dǎo)致很多并發(fā)問(wèn)題,我會(huì)在下面的小節(jié)中解釋。
想像一下這種情形,兩個(gè)或多個(gè)線程同時(shí)訪問(wèn)一個(gè)共享對(duì)象,對(duì)象中包含一個(gè)用于計(jì)數(shù)的變量:

public class SharedObject {
    public int counter = 0;
}

假設(shè) Thread-1 會(huì)增加 counter 的值,而 Thread-1 和 Thread-2 會(huì)不時(shí)地讀取 counter 變量。在這種情形中,如果變量 counter 沒(méi)有被聲明成 volatile,就無(wú)法保證 counter 的值何時(shí)會(huì) (被 Thread-1) 從 CPU cache 寫回到主存。結(jié)果導(dǎo)致 counter 在 CPU 緩存的值和主存中的不一致:

Thread-2 無(wú)法讀取到變量最新的值,因?yàn)?Thread-1 沒(méi)有把更新后的值寫回到主存中。這被稱作 "可見(jiàn)性" 問(wèn)題,即其他線程對(duì)某線程更新操作不可見(jiàn)。

volatile 保證了變量的可見(jiàn)性

volatile 關(guān)鍵字解決了變量的可見(jiàn)性問(wèn)題。通過(guò)把變量 counter 聲明為 volatile,任何對(duì) counter 的寫操作都會(huì)立即刷新到主存。同樣的,所有對(duì) counter 的讀操作都會(huì)直接從主存中讀取。

public class SharedObject {
    public volatile int counter = 0;
}

還是上面的情形,聲明 volatile 后,若 Thread-1 修改了 counter 則會(huì)立即刷新到主存中,Thread-2 從主存讀取的 counter 是 Thread-1 更新后的值,保證了 Thread-2 對(duì)變量的可見(jiàn)性。

volatile 完全可見(jiàn)性

volatile 關(guān)鍵字的可見(jiàn)性生效范圍會(huì)超出 volatile 變量本身,這種完全可見(jiàn)性表現(xiàn)為以下兩個(gè)方面:

如果 Thread-A 對(duì) volatile 變量進(jìn)行寫操作,Thread-B 隨后該 volatile 變量進(jìn)行讀操作,那么 (在 Thread-A 寫 volatile 變量之前的) 所有對(duì) Thread-A 可見(jiàn)的變量,也會(huì) (在 Thread-B 讀 volatile 變量之后) 對(duì) Thread-B 可見(jiàn)。

當(dāng) Thread-A 讀一個(gè) volatile 變量時(shí),所有其他對(duì) Thread-A 可見(jiàn)的變量也會(huì)重新從主存中讀一遍。

很抽象?讓我們舉例說(shuō)明:

public class MyClass {
    private int years;
    private int months
    private volatile int days;
    
    public int totalDays() {
        int total = this.days;
        total += months * 30;
        total += years * 365;
        return total;
    }

    public void update(int years, int months, int days){
        this.years  = years;
        this.months = months;
        this.days   = days;
    }
}

上面的 update() 方法給三個(gè)變量賦值 (寫操作),其中只有 days 是 volatile 變量。完全可見(jiàn)性在這的含義是,當(dāng)對(duì) days 進(jìn)行寫操作時(shí),線程可見(jiàn)的其他變量 (在寫 days 之前的變量) 都會(huì)一同回寫到主存,也就是說(shuō)變量 months 和 years 都會(huì)回寫到主存。

上面的 totalDays() 方法一開(kāi)始就把 volatile 變量 days 讀取到局部變量 total 中,當(dāng)讀取 days 時(shí),變量 months 和 years (在讀 days 之后的變量) 同樣會(huì)從主存中讀取。所以通過(guò)上面的代碼,你能確保讀到最新的 days, months 和 years。

指令重排的困擾

為了提高性能,JVM 和 CPU 會(huì)被允許對(duì)程序進(jìn)行指令重排,只要重排的指令語(yǔ)義保持一致。舉個(gè)例子:

int a = 1;
int b = 2;

a++;
b++;

上述指令可能被重排成如下形式,語(yǔ)義跟先前保持一致:

int a = 1;
a++;

int b = 2;
b++;

然而,當(dāng)你使用了 volatile 變量時(shí),指令重排有時(shí)候會(huì)產(chǎn)生一些困擾。讓我們?cè)倏聪旅娴睦樱?/p>

public class MyClass {
    private int years;
    private int months
    private volatile int days;

    public void update(int years, int months, int days){
        this.years  = years;
        this.months = months;
        this.days   = days;
    }
}

update() 方法在寫變量 days 時(shí),對(duì)變量 years 和 months 的寫操作同樣會(huì)刷新到主存中。但如果 JVM 執(zhí)行了指令重排會(huì)發(fā)生什么情況?就像下面這樣:

public void update(int years, int months, int days){
    this.days   = days;
    this.months = months;
    this.years  = years;
}

當(dāng)變量 days 發(fā)生改變時(shí),months 和 years 仍然會(huì)回寫到主存中。但這一次,days 的更新發(fā)生在寫 months 和 years 之前,導(dǎo)致 months 和 years 的新值可能對(duì)其他線程不可見(jiàn),使程序語(yǔ)義發(fā)生改變。對(duì)此 JVM 有現(xiàn)成的解決方法,我們會(huì)在下一小節(jié)討論這個(gè)問(wèn)題。

volatile 的 Happen-before 機(jī)制

為了解決指令重排帶來(lái)的困擾,Java volatile 關(guān)鍵字在可見(jiàn)性的基礎(chǔ)上提供了 happens-before 這種擔(dān)保機(jī)制。happens-before 保證了如下方面:

如果其他變量的讀寫操作原本發(fā)生在 volatile 變量寫操作之前,他們不能被指令重排到 volatile 變量的寫操作之后。注意,發(fā)生在 volatile 變量寫操作之后的讀寫操作仍然可以被指令重排到 volatile 變量寫操作之前。happen-after 重排到 (volatile 寫操作) 之前是允許的,但 happen-before 重排到之后是不允許的。

如果其他變量的讀寫操作原本發(fā)生在 volatile 變量讀操作之后,他們不能被指令重排到 volatile 變量的讀操作之前。注意,發(fā)生在 volatile 變量讀操作之前的讀操作仍然可以被指令重排到 volatile 變量讀操作之后。happen-before 重排到 (volatile 讀操作) 之后是允許的,但 happen-after 重排到之前是不允許的。

happens-before 機(jī)制確保了 volatile 的完全可見(jiàn)性 (其他文章用到了有序性這個(gè)詞)

volatile 并不總是行得通

雖然關(guān)鍵字 volatile 保證了對(duì) volatile 變量的讀寫操作會(huì)直接訪問(wèn)主存,但在某些情況下把變量聲明為 volatile 還不足夠。
回顧之前舉過(guò)的例子 —— Thread-1 對(duì)共享變量 counter 進(jìn)行寫操作,聲明 counter 為 volatile 并不足以保證 Thread-2 總是能讀到最新的值。

實(shí)際上,可能會(huì)有多個(gè)線程對(duì)同一個(gè) volatile 變量進(jìn)行寫操作,也會(huì)把正確的新值寫回到主存,只要這個(gè)新值不依賴舊值。但只要這個(gè)新值依賴舊值 (也就是說(shuō)線程先會(huì)讀取 volatile 變量,基于讀取的值計(jì)算出一個(gè)新值,并把新值寫回到 volatile 變量),volatile 關(guān)鍵字不再能夠保證正確的可見(jiàn)性 (其他文章會(huì)把這稱為原子性)。

在多線程同時(shí)共享變量 counter 的情形下,volatile 關(guān)鍵字已不足以保證程序的并發(fā)性。設(shè)想一下:Thread-1 從主存中讀取了變量 counter = 0 到 CPU 緩存中,進(jìn)行加 1 操作但還沒(méi)把更新后的值寫回到主存。Thread-2 同一時(shí)間從主存中讀取 counter (值仍為 0) 到他所在的 CPU 緩存中,同樣進(jìn)行加 1 操作,也沒(méi)來(lái)得及回寫到主存。情形如下圖所示:

Thread-1 和 Thread-2 現(xiàn)在處于不同步的狀態(tài)。從語(yǔ)義上來(lái)說(shuō),counter 的值理應(yīng)是 2,但變量 counter 在兩個(gè)線程所在 CPU 緩存中的值卻是 1,在主存中的值還是 0。即使線程都把 counter 回寫到主存中,counter 更新成1,語(yǔ)義上依然是錯(cuò)的。(這種情況應(yīng)該使用 synchronized 關(guān)鍵字保證線程同步)

什么時(shí)候使用 volatile

像之前的例子所說(shuō):如果有兩個(gè)或多個(gè)線程同時(shí)對(duì)一個(gè)變量進(jìn)行讀寫,使用 volatile 關(guān)鍵字是不夠用的,因?yàn)閷?duì) volatile 變量的讀寫并不會(huì)阻塞其他線程對(duì)該變量的讀寫。你需要使用 synchronized 關(guān)鍵字保證讀寫操作的原子性,或者使用 java.util.concurrent 包下的原子類型代替 synchronized 代碼塊,例如:AtomicLong, AtomicReference 等。

如果只有一個(gè)線程對(duì)變量進(jìn)行讀寫操作,其他線程僅有操作,這時(shí)使用 volatile 關(guān)鍵字就能保證每個(gè)線程都能讀到變量的最新值,即保證了可見(jiàn)性。

volatile 的性能

volatile 變量的讀寫操作會(huì)導(dǎo)致對(duì)主存的直接讀寫,對(duì)主存的直接訪問(wèn)比訪問(wèn) CPU 緩存開(kāi)銷更大。使用 volatile 變量一定程度上影響了指令重排,也會(huì)一定程度上影響性能。所以當(dāng)迫切需要保證變量可見(jiàn)性的時(shí)候,你才會(huì)考慮使用 volatile。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/71947.html

相關(guān)文章

  • Java多線程學(xué)習(xí)(三)volatile關(guān)鍵字

    摘要:三關(guān)鍵字能保證原子性嗎并發(fā)編程藝術(shù)這本書上說(shuō)保證但是在自增操作非原子操作上不保證,多線程編程核心藝術(shù)這本書說(shuō)不保證。多線程訪問(wèn)關(guān)鍵字不會(huì)發(fā)生阻塞,而關(guān)鍵字可能會(huì)發(fā)生阻塞關(guān)鍵字能保證數(shù)據(jù)的可見(jiàn)性,但不能保證數(shù)據(jù)的原子性。 系列文章傳送門: Java多線程學(xué)習(xí)(一)Java多線程入門 Java多線程學(xué)習(xí)(二)synchronized關(guān)鍵字(1) java多線程學(xué)習(xí)(二)synchroniz...

    tain335 評(píng)論0 收藏0
  • Java中的Volatile關(guān)鍵字

    摘要:變量可見(jiàn)性問(wèn)題的關(guān)鍵字保證了多個(gè)線程對(duì)變量值變化的可見(jiàn)性。只要一個(gè)線程需要首先讀取一個(gè)變量的值,基于這個(gè)值生成一個(gè)新值,則一個(gè)關(guān)鍵字不足以保證正確的可見(jiàn)性。 Java的volatile關(guān)鍵字用于標(biāo)記一個(gè)Java變量為在主存中存儲(chǔ)。更確切的說(shuō),對(duì)volatile變量的讀取會(huì)從計(jì)算機(jī)的主存中讀取,而不是從CPU緩存中讀取,對(duì)volatile變量的寫入會(huì)寫入到主存中,而不只是寫入到CPU緩存...

    JohnLui 評(píng)論0 收藏0
  • 死磕 java同步系列之volatile解析

    摘要:前半句是指線程內(nèi)表現(xiàn)為串行的語(yǔ)義,后半句是指指令重排序現(xiàn)象和工作內(nèi)存和主內(nèi)存同步延遲現(xiàn)象。關(guān)于內(nèi)存模型的講解請(qǐng)參考死磕同步系列之。目前國(guó)內(nèi)市面上的關(guān)于內(nèi)存屏障的講解基本不會(huì)超過(guò)這三篇文章,包括相關(guān)書籍中的介紹。問(wèn)題 (1)volatile是如何保證可見(jiàn)性的? (2)volatile是如何禁止重排序的? (3)volatile的實(shí)現(xiàn)原理? (4)volatile的缺陷? 簡(jiǎn)介 volatile...

    番茄西紅柿 評(píng)論0 收藏0
  • 死磕 java同步系列之volatile解析

    摘要:前半句是指線程內(nèi)表現(xiàn)為串行的語(yǔ)義,后半句是指指令重排序現(xiàn)象和工作內(nèi)存和主內(nèi)存同步延遲現(xiàn)象。關(guān)于內(nèi)存模型的講解請(qǐng)參考死磕同步系列之。目前國(guó)內(nèi)市面上的關(guān)于內(nèi)存屏障的講解基本不會(huì)超過(guò)這三篇文章,包括相關(guān)書籍中的介紹。問(wèn)題 (1)volatile是如何保證可見(jiàn)性的? (2)volatile是如何禁止重排序的? (3)volatile的實(shí)現(xiàn)原理? (4)volatile的缺陷? 簡(jiǎn)介 volatile...

    番茄西紅柿 評(píng)論0 收藏0
  • 死磕 java同步系列之volatile解析

    摘要:前半句是指線程內(nèi)表現(xiàn)為串行的語(yǔ)義,后半句是指指令重排序現(xiàn)象和工作內(nèi)存和主內(nèi)存同步延遲現(xiàn)象。關(guān)于內(nèi)存模型的講解請(qǐng)參考死磕同步系列之。目前國(guó)內(nèi)市面上的關(guān)于內(nèi)存屏障的講解基本不會(huì)超過(guò)這三篇文章,包括相關(guān)書籍中的介紹。問(wèn)題 (1)volatile是如何保證可見(jiàn)性的? (2)volatile是如何禁止重排序的? (3)volatile的實(shí)現(xiàn)原理? (4)volatile的缺陷? 簡(jiǎn)介 volatile...

    kviccn 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<