java中的volatile变量和内存障碍

我有一个由链接节点组成的数据结构。 您可以将其视为简单的LinkedList。 列表的每个节点由一些值和指向另一个节点的下一个字段组成,如果它是最后一个节点则为null。 第一个节点作为根,它没有值,只指向下一个节点。 所有其他节点实际上是不可变的,一旦它们被创建,它们的值和它们的下一个字段在生命期间都不会改变,除非正在处理与特定情况有关的结构。

一个(仅一个)线程将新节点添加到列表的前面。 它是通过构造一个新对象,设置其字段并将下一个字段设置为根指向的对象,然后将根的下一个字段设置为此新节点来实现的。

其他节点浏览仅执行读取的结构。 它们具有对根节点的引用,然后它们通过其他节点,直到它们找到正在查找的内容或到达列表的末尾。

我的问题是:是否足以让下一个字段变得易变? 根据我对java内存模型的理解,如果主线程(添加新节点的线程)在添加新节点时将执行易失性写入,那么一切都将很好地同步,并且不会发生任何不一致。

也是正确的假设在x86架构上读取volatile变量不会导致任何性能下降? 由于其他线程经常浏览读取下一个字段的结构,因此可以自由地完成此操作而不需要任何内存屏障等。

我还有一个问题。 要浏览结构的线程也将保留一些额外的节点。 这些节点将完全是线程本地的,它们将仅由创建它们的线程使用,并且根本不会被共享。 对于这些附加节点,下一个字段不必是易失性的。 此外,设置易失性下一字段将发出存储器屏障,这将导致不期望的性能损失。 我想知道有没有办法避免这种情况。 理想情况下,如果下一个字段有时会作为一个易失性字段而有时作为一个普通字段工作,那将是完美的;)或者如果我有完全控制并且可以在我需要的时候自行发出内存屏障。

编辑:

我也想知道是否有可能以不同的方式在不同的volatile变量上同步所有这些写入? 例如一些其他完全不相关的静态变量? 由于volatile写入刷新所有挂起的写入,下一个字段是不是可能不是易失性的,而是在更新线程完成所有工作后会写入不同的volatile变量?

它对我来说看起来不太安全,因为在关系之前没有发生,之前的写入可能会被重新排序。 可以使用值字段分配重新下载下一个字段分配,从而导致迭代线程观察到不一致的对象状态。

但也许有可能提出这样一个安全的方案吗? 这个怎么样:

更新线程首先构造一个新对象,初始化其值字段,将其下一个字段设置为根节点指向的节点, 对某个静态变量执行易失性写入 ,将根节点的下一个字段设置为新创建的节点

1。

根据你在这里说的话

构造一个新对象,设置其字段并将下一个字段设置为根指向的对象,然后将根的下一个字段设置为此新节点。

然后是的,将下一个字段设置为volatile将正确同步。 了解原因很重要。 你手头有三组写,一组是节点对象,一组是字段,另一组是接下来的节点(虽然不完全确定你为什么这样做,也许我想念一些东西)。

所以这是2 +(N个字段)写入。 此时没有发生在之前的关系,如果节点正常写入,则无法保证。 一旦写入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 jucLock locking) 

3。

看起来你必须创建VolatileNode和Node。 有一个关于Java 7的提议出来了Fences API,您可以使用静态实用程序类指定要执行的读/写样式,但看起来不像它的释放

编辑:

Thkala提出了一个值得包括的重点

虽然应该指出前JSR133 JVM(即Java <5.0)没有相同的语义

所以我写的内容不适用于在Java 1.4或更低版本中运行的应用程序。

使next字段volatile会对节点类的所有实例施加内存屏障,而不仅仅是根节点。 我希望这比在根节点上使用synchronized更昂贵。 此外,JVM可能能够更好地优化synchronized方法调用。 另见这个和这个 。

也就是说,你应该尝试两者和基准/配置文件来看看会发生什么。

您的根节点实际上不需要是节点。 您只需要引用第一个“真实”节点。

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

因此,您不需要在所有节点中使next字段变为volatile; 节点根本不同步。 如果您不介意对第一个节点进行易失性访问的成本,则可以将该类用于非同步链接列表。 或者您可以简单地使用非同步版本的非易失性firstNode重写该类。