簡體   English   中英

java 中的 volatile 變量和 memory 屏障

[英]volatile variables and memory barrier in java

我有一個由鏈接節點組成的數據結構。 您可以將其視為一個簡單的 LinkedList。 列表的每個節點都包含一些值和指向另一個節點的下一個字段,如果它是最后一個節點,則為 null。 第一個節點作為根工作,它沒有任何值,它只指向下一個節點。 所有其他節點實際上是不可變的,即一旦它們被創建,它們的值和它們的下一個字段在生命周期內都不會改變,除非正在處理與特定情況相關的結構。

一個(只有一個)線程將新節點添加到列表的前面。 它是通過構造一個新的 object,設置它的字段並將下一個字段設置為根指向的 object,然后將根的下一個字段設置為這個新節點來完成的。

其他節點瀏覽僅執行讀取的結構。 他們有一個對根節點的引用,然后他們通過其他節點 go 直到他們找到正在尋找的內容或到達列表的末尾。

我的問題是:是否足以使下一個字段易變? 根據我對 java memory model 的理解,如果主線程(添加新節點的那個)在添加新節點時將執行一個很好的易失性寫入,那么一切都不會發生。

還可以假設在 x86 架構上讀取 volatile 變量不會導致任何性能下降是正確的嗎? 由於其他線程會經常瀏覽讀取下一個字段的結構,因此可以在沒有任何 memory 障礙等的情況下自由完成這一點很重要。

我還有一個擔憂。 將要瀏覽結構的線程也將持有一些額外的節點。 這些節點將完全是線程本地的,即它們將僅由創建它們的線程使用,並且根本不會被共享。 對於這些額外的節點,下一個字段沒有必要是易失的。 此外,設置易失性下一個字段將發出 memory 屏障,這將導致不良的性能損失。 我想知道有沒有辦法避免這種情況。 理想情況下,如果下一個字段有時作為易失字段工作,有時作為正常字段工作,那將是完美的;)或者如果我有完全控制權並且可以在我需要時自行發出 memory 屏障。

編輯:

我還想知道是否有可能以某種方式將所有這些寫入同步到不同的 volatile 變量上? 例如其他一些完全不相關的 static 變量? 由於 volatile 寫入會刷新所有掛起的寫入,難道下一個字段不是 volatile 而是在更新線程完成所有工作后寫入不同的 volatile 變量嗎?

對我來說它看起來不太安全,因為在關系之前沒有發生任何事情,並且之前的寫入可能會被重新排序。 下一個字段分配可以使用值字段分配重新排序,導致迭代線程觀察不一致的 object state。

但也許有可能想出這樣一個安全的方案? 這個怎么樣:

更新線程首先構造一個新的 object,初始化其值字段,將其下一個字段設置為根節點指向的節點,對一些 static 變量執行易失性寫入,將根節點的下一個字段設置為新創建的節點

1.

根據你在這里說的

構造一個新的object,設置它的字段並將下一個字段設置為根指向的object,然后將根的下一個字段設置為這個新節點。

那么是的,將下一個字段設置為 volatile 將正確同步。 了解原因很重要。 您之前有三組寫入,一組寫入節點 object,一組寫入字段,一組寫入下一個節點(雖然不完全確定您為什么要這樣做,也許我錯過了一些理解)。

所以這是 2 +(N 個字段)寫入。 此時沒有happens-before關系,如果節點寫入正常,則無法保證。 一旦您寫入 volatile 字段,所有以前的寫入現在也將可見。

2.

x86(或任何緩存一致)操作系統上的易失性讀/寫具有以下屬性:

 volatile-read: very close to a normal read volatile-write: about 1/3 the time of a synchronization write (whether within intrinsic locking or juc.Lock locking)

3.

看起來您將不得不創建 VolatileNode 和 Node.js。 There was a proposal for Java 7 to come out with a Fences API which you can specify which style of reading/write you want to execute with a static utility class but doesn't look like its releasing

編輯:

Thkala 提出了一個很好的觀點,我覺得值得包括

盡管應該指出,pre-JSR133 JVM(即 Java < 5.0)沒有相同的語義

所以我寫的不適用於在 Java 1.4 或更低版本中運行的應用程序。

使next字段volatile將對節點 class 的所有實例施加 memory 屏障,而不僅僅是根節點。 我希望這比僅在根節點上使用synchronized更昂貴。 此外,JVM 可能能夠更好地優化synchronized方法調用。 另請參見thisthis

也就是說,您可能應該同時嘗試基准測試/配置文件以查看會發生什么。

您的根節點實際上不需要是節點。 您只需要引用第一個“真實”節點。

public class LinkedList {
  private volatile Node firstNode;
  ...
  addNode(Node node) {
    node.next = firstNode;
    firstNode = node;
  }
}

因此,您不需要在所有節點中設置next字段 volatile; 節點根本不同步。 如果您不介意對第一個節點進行易失性訪問的成本,您可以將 class 用於非同步鏈表。 或者,您可以簡單地用非易失性firstNode重寫 class 以用於非同步版本。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM