国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Java中的Volatile關鍵字

JohnLui / 768人閱讀

摘要:變量可見性問題的關鍵字保證了多個線程對變量值變化的可見性。只要一個線程需要首先讀取一個變量的值,基于這個值生成一個新值,則一個關鍵字不足以保證正確的可見性。

Java的volatile關鍵字用于標記一個Java變量為“在主存中存儲”。更確切的說,對volatile變量的讀取會從計算機的主存中讀取,而不是從CPU緩存中讀取,對volatile變量的寫入會寫入到主存中,而不只是寫入到CPU緩存。

實際上,從Java5開始,volatile關鍵字不只是保證了volatile變量在主存中寫入和讀取,我回在后面的部分做相關的解釋。

變量可見性問題

Java的volatile關鍵字保證了多個線程對變量值變化的可見性。這聽起來有點抽象,讓我來詳細解釋。

在一個多線程的程序中,當多個線程操作非volatile變量時,出于性能原因,每個線程會從主存中拷貝一份變量副本到一個CPU緩存中。如果你的計算機有多于一個CPU,每個線程可能會在不同的CPU中運行。這意味著每個簡稱拷貝變量到不同CPU的緩存中,如下圖:

對于非volatile變量,并沒有保證何時JVM從主存中讀取數據到CPU緩存,或者從CPU緩存中寫出數據到主存。這會導致一些問題。

想象一種情況,多于一個線程訪問一個共享對象,這個共享對象包含一個計數變量如下聲明:

public class ShareObject {
  public int counter = 0;
}

考慮只有一個線程Thread1增加counter這個變量的值,但是Tread1和Thread2可能有時會讀取counter變量。

如果counter變量沒有被聲明為volatile,就不能保證何時這個變量的值會從CPU緩存寫回主存,這意味著,在CPU緩存中的counter變量的值可能和主存中的不一樣。如下圖所示:

線程沒有看到一個變量最新更新的值的原因是這個變量還沒有被一個線程寫回到主存,這被稱為“可見性”問題。一個線程對變量的更新對其他線程不可見。

Java的volatile可見性保證

Java的volatile關鍵字想要解決變量可見性問題。通過聲明counter變量為volatile,所有對counter變量的寫入都回立即寫回到主存,同時所有對counter變量也都會從主存中讀取。

西面的代碼展示了如何把counter變量聲明為volatile:

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

聲明一個變量為volatile保證了對變量的寫入對其他線程的可見性。

在上面的場景中,一個線程(T1)修改了counter變量的值,另一個線程(T2)讀取counter變量(但是不修改它),聲明counter變量為volatile足以保證對counter變量的寫入對T2可見。

但是,如果T1和T2都去增加counter變量的只,name聲明counter變量為volatile是不夠的,后面會說明。

全volatile可見性保證

實際上,Java的volatile的可見性保證不止volatile變量本身。可見性保證如下:

如果線程A寫一個volatile變量,線程B隨后讀取這個volatile變量,那么在寫這個volatile變量之前對線程A可見的所有變量,在線程B讀取這個volatile變量之后對線程B也可見。

如果線程A讀取一個volatile變量,那么當A讀取這個volatile變量時所有對線程A可見的變量也可以從主存中再次讀取。

我用下面的代碼來說明:

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變量是volatile的。

全volatile可見性保證的意思是,當一個值寫入到days變量,則所有對當前線程可見的變量也會都寫入到主存,也就是當一個值寫入到days變量,則years和months的只也被寫入到主存。

當讀取years,months和days的值,可以這樣做:

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;
  }
}

需要注意的是totalDays()方法起始于讀取days的值到total變量中。當讀取days的值時,months和years的值也被讀取到主存。因此可以保證你看到的是days,months和years的最新的值,前提是保證上面的讀取順序。

指令重排序挑戰

出于性能的考量,JVM和CPU允許對程序中的指令進行重排序,只要指令的語義不變。例如下面的指令:

int a = 1;
int b = 2;

a++;
b++;

這些指令可以按照下面的順序重排,并不會丟失程序的語義:

int a = 1;
a++;

int b = 2;
b++;

但是,指令重排序對于其中一個變量是volatile變量這種情況是有挑戰的。讓我們看一下MyClass這個類:

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變量寫入一個值,years和months新寫入的只也刷入到主存,但是,如果有JVM指令重排序,像下面這樣:

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

months和years的只在days變量修改的情況下依然會寫入到主存,但是這時將years和days變量值刷入主存這件事發生在對months和years寫入新值之前,則對years和days的更新對其他線程來說就不可見了。這下指令重排序就改變了程序的語義。

Java有一個應對此問題的解決方案,下面會講到。

Java的volatile的Happens-Before保證

為了解決指令重排序的挑戰,Java的volatile關鍵字除了可見性保證之外,給出了一個“happens-before”的保證。happens-before保證如下情況:

如果讀取和寫入其他非volatile變量發生在寫入volatile變量之前(這種情況這些非volatile變量也會被刷入主存),則讀取和寫入這些變量不能被重排序為發生在寫入這個volatile變量之后(禁止指令重排序)。在寫入一個volatile變量之前的讀取和寫入非volatile變量被保證為“happen before”寫入這個volatile變量。需要注意的是,例如在寫入一個volatile變量之后讀寫其他變量可以被重排序到寫入這個volatile變量之前。從“之后”重排序到”之前“是允許的,但是從”之前“重排序到”之后“是禁止的。

如果讀寫其他非volatile變量發生在讀取一個volatile變量之后(這種情況這些非volatile變量也會被刷到主存),則讀寫這些變量不能被重排序為發生在讀取這個volatile變量之前。需要注意的是,讀取其他變量發生在讀取一個volatile變量之前能夠被重排序為發生在讀取這個volatile變量之后。從”之前“重排序到“之后”是允許的,但是從“之后”重排序到“之前”是被禁止的。

上面的happens-before保障保證的volatile關鍵字的可見性是強制的。

volatile不總是足夠的

盡管volatile關鍵字保證了所有對一個volatile變量的讀取都是從主存中讀取,所有對volatile關鍵字的寫入都是直接到主存,但是仍有其他情況使得聲明一個變量為volatile是不足夠的。

在前面解釋的情況,也就是只有Thread1寫共享變量counter,聲明counter變量為volatile足以保證Thread2總是看到最新寫入的值。

實際上,多線程都可以寫一個共享的volatile變量,并且仍然在主存中存儲正確的值,前提是寫入變量的新值不依賴于它之前的值。也就是說,如果一個線程寫入一個值到共享的volatile變量不需要先去讀它的值去產出下一個值。

只要一個線程需要首先讀取一個volatile變量的值,基于這個值生成一個新值,則一個volatile關鍵字不足以保證正確的可見性。在讀取volatile變量然后寫入新值的短暫的間隙,會產生競態條件(race condition),這時多個線程可能讀取到相同的volatile變量的值,生成這個變量的新值,當將新值寫回主存時,會覆蓋彼此的值。

多線程增加相同計數器的值就是這種情況,導致一個volatile聲明不足夠。下面詳細解釋這種情況。

想象如果Thread1讀取一個值為0的共享的counter變量到它的CPU緩存,增加1并且不將這個改變的值寫回主存。Thread2然后從主存中讀取相同的值仍為0counter變量到它的CPU緩存。Thread2也為它增加1,也不寫回主存。這種情況如下圖所示:

Thread1和Thread2此時實際上已經不同步了。共享變量counter的值應該為2,但是每個線程在CPU緩存中的這個變量的值都為1,在主存中的值仍為0,這就亂了!盡管這兩個線程最終會將值寫回主存中的共享變量,這個值也是不正確的。

何時volatile是足夠的?

正如前面所說,如果兩個線程都去讀寫同一個共享變量,只對這個共享變量使用volatile關鍵字是不夠的。你需要使用一個synchronized關鍵字去保證讀寫相同變量是原子的。讀寫一個volatile變量不會阻塞線程的讀寫。

作為synchronized塊替代方法,你可以使用java.util.concurrent包中的眾多原子數據類型。比如,AtomicLong或者AtomicReference或其他的類型。

只有一個線程讀寫一個volatile變量值,其他線程只讀取變量,則這些讀線程能夠保證看到寫入這個volatile變量的最新值,如果不聲明為volatile,則這種情況不能保證。

volatile的性能考量

讀寫volatile變量會導致變量被讀寫到主存。讀寫主存比訪問CPU緩存開銷更大。訪問volatile變量也會禁止指令重排序,而指令重排序是一個正正常的性能優化技術。因此,你應該只在真正需要保證變量可見性的時候使用volatile變量。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/75950.html

相關文章

  • Java多線程學習(三)volatile鍵字

    摘要:三關鍵字能保證原子性嗎并發編程藝術這本書上說保證但是在自增操作非原子操作上不保證,多線程編程核心藝術這本書說不保證。多線程訪問關鍵字不會發生阻塞,而關鍵字可能會發生阻塞關鍵字能保證數據的可見性,但不能保證數據的原子性。 系列文章傳送門: Java多線程學習(一)Java多線程入門 Java多線程學習(二)synchronized關鍵字(1) java多線程學習(二)synchroniz...

    tain335 評論0 收藏0
  • 死磕 java同步系列之volatile解析

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

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

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

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

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

    kviccn 評論0 收藏0
  • java volatile 鍵字

    摘要:舉個例子,在多線程不使用環境中,每個線程會從主存中復制變量到緩存以提高性能。保證了變量的可見性關鍵字解決了變量的可見性問題。在多線程同時共享變量的情形下,關鍵字已不足以保證程序的并發性。 volatile 關鍵字能把 Java 變量標記成被存儲到主存中。這表示每一次讀取 volatile 變量都會訪問計算機主存,而不是 CPU 緩存。每一次對 volatile 變量的寫操作不僅會寫到 ...

    scola666 評論0 收藏0

發表評論

0條評論

JohnLui

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<