java中的易失性变量和内存屏障

发布于 2024-11-17 12:50:21 字数 1128 浏览 3 评论 0原文

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

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

其他节点浏览该结构,仅执行读取操作。它们有对根节点的引用,然后它们遍历其他节点,直到找到要查找的内容或到达列表的末尾。

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

另外,假设在 x86 架构上读取 易失性变量不会导致任何性能下降是否正确?由于其他线程会频繁地浏览结构并读取下一个字段,因此可以在没有任何内存障碍等情况下自由完成此操作,这一点很重要。

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

编辑:

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

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

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

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

I've got a data structure which consists of linked nodes. You can think of it as of a simple LinkedList. Each node of the list consists of some value and a next field pointing the other node or null if it is the last node. The first node works as a root, it has no value it only points to the next node. All the other nodes are practically immutable that is once they are created neither their value nor their next field change during lifetime, unless the structure is being disposed which relates to a specific situation.

One (only one) thread adds new nodes to the front of the list. It is accomplished by 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.

The other nodes browse through the structure only performing reads. They have a reference to the root node, then they go through the other nodes until they find what are looking for or reach the end of the list.

My question is: is it sufficient to make the next field volatile? From my understanding of java memory model, if the main thread (the one that adds new nodes) will perform a volatile write when adding a new node then everything will be synchronized just fine and no inconsistencies will occur.

Also is it right to assume that on x86 architecture reads of a volatile variable won't incur any performance degradation? As the other threads will frequently browse through the structure reading the next field it is important that this can be done freely without any memory barriers etc.

I also have one more concern. The threads that are going to browse the structure are also going to hold some additional nodes. These nodes will be completely thread-local that is they are going to be used by only the thread that created them and are not going to be shared at all.
For these additional nodes it is unnecessary for the next field to be volatile. Moreover setting the volatile next field will issue a memory barrier which will cause an undesirable performance loss.
I wonder is there a way to avoid this. Ideally it would be just perfect if the next field would work sometimes as a volatile field and sometimes as a normal field ;) or if I had a full control and could issue memory barriers on my own, whenever I need.

Edit:

I also wondered would it be possible to somehow synchronize all these writes on a different volatile variable? For example some other completely unrelated static variable? Since volatile write flushes all the pending writes, wouldn't it be possible for the next field not to be volatile and instead a different volatile variable would be written after the updating thread does all the work?

It does not look very safe to me since there is no happens before relation and the previous writes might get reordered. Next field assignments could be reoredered with the value fields assignments leading to iterating threads observing inconsistent object state.

But maybe it is possible to come up with such a scheme that would be safe? How about this one:

updating thread first constructs a new object, initializes its value fields, sets its next field to node pointed by the root node, performs a volatile write on some static variable, sets the next field of the root node to the newly created node

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

守望孤独 2024-11-24 12:50:21

1.

根据你所说的

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

那么是的,将下一个字段设置为 易失性 将正确同步。了解原因很重要。您事先有三组写入,一组写入节点对象,一组写入字段,一组写入下一个节点(虽然不完全确定您为什么这样做,也许我错过了一些理解)。

所以这是 2 +(N 个字段)写入。此时不存在happens-before关系,并且无法保证节点是否正常写入。一旦您写入易失性字段,所有以前的写入现在也将可见。

2.

x86(或任何缓存一致性)操作系统上的易失性读/写具有以下属性:

 易失性读取:非常接近正常读取
 易失性写入:大约是同步写入时间的1/3 
         (无论是内在锁定还是 jucLock 锁定)

3.

看来您必须创建 VolatileNode 和 Node。有人提议 Java 7 推出一个 Fences API,您可以指定要使用静态实用程序类执行哪种读/写风格,但看起来不像它的发布

编辑:

Thkala 提出了一个很好的观点,我认为值得包括

虽然需要指出的是
JSR133 之前的 JVM(即 Java < 5.0)确实如此
没有相同的语义

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

1.

Based on what you say here

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.

Then yes, setting the next field to volatile will correctly synchronize. Its important to understand why. You have three sets of writes before hand, the one to the node object, one to the fields and one to the nodes next (though not completely sure why you are doing that, maybe I miss understand something).

So that's 2 + (N number of field) writes. At this point there is no happens-before relationship and if the node is written normally there is no guarantee. As soon as you write to the volatile field all previous writes will now also be visible.

2.

Volatile reads/writes on a x86 (or any cache-coherent) operating system has the following attributes:

 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.

Looks like you will have to create VolatileNode and Node. 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

Edit:

Thkala made a great point I feel is worth including

although it should be pointed out that
pre-JSR133 JVMs (i.e. Java < 5.0) did
not have the same semantics

So what I wrote does not apply to applications run in Java 1.4 or less.

如果没有你 2024-11-24 12:50:21

next 字段设置为 易失性 将会对节点类的所有实例(而不仅仅是根节点)施加内存屏障。我预计这比在根节点上使用 synchronized 更昂贵。此外,JVM 可能能够更好地优化同步方法调用。另请参阅这个

也就是说,您可能应该同时尝试基准测试/配置文件,看看会发生什么。

Making the next field volatile would impose a memory barrier on all instances of the node class, not just the root node. I'd expect that to be more expensive than just using synchronized on the root node. In addition, the JVM may be able to optimize synchronized method calls far better. See also this and this.

That said, you should probably try both and benchmark/profile to see what happens.

野生奥特曼 2024-11-24 12:50:21

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

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

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

Your root node actually does not need to be a node. You only need a reference to the first "real" node.

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

So you don't need to make the next field volatile in all your nodes; the nodes are not synchronized at all.
You could use that class for the non-synchronized linked lists if you don't mind the cost of the volatile access to the first node. Or you could instead simply rewrite the class with a non-volatile firstNode for the non-synchronized version.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文