我有一个由链接节点组成的数据结构。您可以将其视为一个简单的 LinkedList。列表的每个节点都包含一些值和指向另一个节点的 next 字段,如果它是最后一个节点则为 null。第一个节点作为根,它没有值,它只指向下一个节点。所有其他节点实际上都是不可变的,一旦它们被创建,它们的值和它们的下一个字段在生命周期内都不会改变,除非正在处理与特定情况相关的结构。
一个(只有一个)线程将新节点添加到列表的前面。它是通过构造一个新对象,设置它的字段并将下一个字段设置为根指向的对象,然后将根的下一个字段设置为这个新节点来完成的。
其他节点浏览仅执行读取的结构。它们具有对根节点的引用,然后遍历其他节点,直到找到要查找的内容或到达列表的末尾。
我的问题是:是否足以使下一个字段易变?根据我对 java 内存模型的理解,如果主线程(添加新节点的线程)在添加新节点时执行 volatile 写入,那么一切都会很好地同步并且不会发生不一致。
此外,假设在 x86 架构上读取 volatile 变量不会导致任何性能下降是否正确?由于其他线程会频繁浏览读取下一个字段的结构,因此可以在没有任何内存障碍等的情况下自由完成此操作很重要。
我还有一个问题。将要浏览该结构的线程也将持有一些额外的节点。这些节点将完全是线程本地的,也就是说它们将仅由创建它们的线程使用并且根本不会共享。 对于这些额外的节点,下一个字段没有必要是易失的。此外,设置 volatile next 字段将发出内存屏障,这将导致不希望的性能损失。 我想知道有没有办法避免这种情况。理想情况下,如果下一个字段有时作为可变字段工作,有时作为普通字段工作,那将是完美的;)或者如果我有完全的控制权并且可以在我需要的时候自己发出内存屏障。
编辑:
我还想知道是否有可能以某种方式将所有这些写入同步到不同的 volatile 变量上?例如其他一些完全不相关的静态变量?由于 volatile write 会刷新所有挂起的写入,难道下一个字段不是 volatile 而是在更新线程完成所有工作后会写入一个不同的 volatile 变量是不可能的吗?
这对我来说看起来不太安全,因为关系之前没有发生任何事情,并且之前的写入可能会被重新排序。可以使用值字段分配重新排序下一个字段分配,从而导致迭代线程观察到不一致的对象状态。
但也许有可能想出这样一个安全的方案?这个怎么样:
更新线程首先构造一个新对象,初始化它的值字段,将它的下一个字段设置为根节点指向的节点,对某个静态变量执行 volatile 写入,设置下一个字段根节点到新创建的节点
最佳答案
1.
根据你在这里说的
constructing a new object, setting its fields and setting the next field to the object pointed by the root, then setting the root's next field to this new node.
那么是的,将下一个字段设置为 volatile 将正确同步。了解原因很重要。你手头有三组写入,一组写入节点对象,一组写入字段,一组写入下一个节点(虽然不完全确定你为什么这样做,也许我想念一些东西)。
所以这是 2 +(N 个字段)写入。此时没有 happens-before 关系,如果节点正常写入则无法保证。一旦您写入 volatile 字段,所有之前的写入现在也将可见。
2.
x86(或任何缓存一致)操作系统上的 volatile 读/写具有以下属性:
volatile-read: very close to a normal read volatile-write: about 1/3 the time of a synchronization write (whether within intrinsic locking or j.u.c.Lock locking)
3.
看起来您必须创建 VolatileNode 和 Node。有人建议 Java 7 提出 Fences。 API,您可以指定要使用静态实用程序类执行哪种读/写样式,但看起来不像它的发布
编辑:
Thkala 提出了一个很好的观点,我觉得值得包括在内
although it should be pointed out that pre-JSR133 JVMs (i.e. Java < 5.0) did not have the same semantics
所以我写的内容不适用于在 Java 1.4 或更低版本中运行的应用程序。
关于java - java中的volatile变量和内存屏障,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6524366/