Java volatile 關鍵字

當你的 Java 數據類型足夠大(在 Java 中 long 和 double 類型都是 64 位),(32 位 JDK)寫入變量的過程會分兩步進行,就會發生 Word tearing (字分裂)情況。 JVM 被允許將 64 位數量的讀寫作為兩個單獨的 32 位操作執行,這增加了在讀寫過程中發生上下文切換的可能性,因此其他任務會看到不正確的結果。這被稱為 Word tearing (字分裂),因為你可能只看到其中一部分修改後的值。基本上,任務有時可以在第一步之後但在第二步之前讀取變量,從而產生垃圾值(對於例如 boolean 或 int 類型的小變量是沒有問題的;任何 long 或 double 類型則除外)

在缺乏任何其他保護的情況下,用 volatile 修飾符定義一個 long 或 double 變量,可阻止字分裂情況。然而,如果使用 synchronized 或 java.util.concurrent.atomic 庫之一保護這些變量,則 volatile 將被取代。此外, volatile 不會影響到增量操作並不是原子操作的事實(例如 i++ )

可見性

在一個多線程的應用中,線程在操作非 volatile 變量時,出於性能考慮,每個線程可能會將變量從主存拷貝到 CPU 緩存中。如果你的計算機有多個 CPU,每個線程可能會在不同的 CPU 中運行。這意味著,每個線程都有可能會把變量拷貝到各自 CPU 的緩存中。 出現這個問題是因為 Java 嘗試儘可能地提高執行效率,緩存的主要目的是避免從主內存中讀取數據。當併發時,有時不清楚 Java 什麼時候應該將值從主內存刷新到本地緩存 — 而這個問題稱為 緩存一致性 ( cache coherence )

Java volatile 關鍵字

每個線程都可以在處理器緩存中存儲變量的本地副本。將字段定義為 volatile 可以防止這些編譯器優化,這樣讀寫就可以直接進入內存,而不會被緩存。一旦該字段發生寫操作,所有任務的讀操作都將看到更改。如果一個 volatile 字段剛好存儲在本地緩存,則會立即將其寫入主內存,並且該字段的任何讀取都始終發生在主內存中

volatile 應該在何時適用於變量:

<code>java.util.concurrent.atomic/<code>

重要的是要理解原子性和可見性是兩個不同的概念,在非 volatile 變量上的原子操作是不能保證是否將其刷新到主內存的

同步也會讓主內存刷新,所以如果一個變量由 synchronized 的方法或代碼段(或者 java.util.concurrent.atomic 庫裡類型之一)所保護,則不需要讓變量用 volatile

重排與 Happen-Before 原則

只要結果不會改變程序表現,Java 可以通過重排指令來優化性能。然而,重排可能會影響本地處理器緩存與主內存交互的方式,從而產生細微的程序 bug 。 volatile 關鍵字可以阻止重排 volatile 變量周圍的讀寫指令。這種重排規則稱為 happens before 擔保原則

happens-before 原則保證在 volatile 變量讀寫之前發生的指令先於它們的讀寫之前發生;同樣,任何跟隨 volatile 變量之後讀寫的操作都保證發生在它們的讀寫之後,例如:

<code>// lowlevel/ReOrdering.javapublic class ReOrdering implements Runnable {  int one, two, three, four, five, six;  volatile int volaTile;  @Override  public void run() {    one = 1;    two = 2;    three = 3;    volaTile = 92;    int x = four;    int y = five;    int z = six;  }}複製代碼/<code>

例子中 one , two , three 變量賦值操作可以被重排,但它們都發生在 volatile 變量寫操作之前。同樣,只要 volatile 變量寫操作發生在所有語句之前, x , y , z 語句也可以被重排。這種 volatile (易變性)操作通常稱為 memory barrier (內存屏障)。 happens before 擔保原則確保 volatile 變量的讀寫指令不能跨過內存屏障進行重排

happens before 擔保原則還有另一個作用:當線程向一個 volatile 變量寫入時,在線程寫入之前的其他所有變量(包括非 volatile 變量)也會刷新到主內存。當線程讀取一個 volatile 變量時,它也會讀取其他所有變量(包括非 volatile 變量)與 volatile 變量一起刷新到主內存。儘管這是一個重要的特性,它解決了 Java 5 版本之前出現的一些非常狡猾的 bug ,但是你不應該依賴這項特性來“自動”使周圍的變量變得易變性(volatile)


分享到:


相關文章: