繁体   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