内存栅栏在 Java 中的用途是什么?

在试图理解(OpenJDK 10,Javadoc中的源代码)是如何实现的(在版本9中添加到Java SE中的新类)时,我偶然发现了一些我以前不知道的对VarHandle的API调用:SubmissionPublisher

fullFence和。acquireFencereleaseFenceloadLoadFencestoreStoreFence

在做了一些研究之后,特别是关于记忆障碍/栅栏的概念(我以前听说过它们,是的;但从未使用过它们,因此对它们的语义非常不熟悉),我想我对它们的用途有了基本的了解。尽管如此,由于我的问题可能源于误解,我想确保我首先做对了:

  1. 内存障碍是有关读取和写入操作的重新排序约束。

  2. 内存屏障可分为两大类:单向和双向内存屏障,具体取决于它们是对读取还是写入或两者都设置约束。

  3. C++支持各种内存屏障,但是,这些屏障与 提供的内存屏障不匹配。但是,VarHandle 中提供的一些内存屏障提供的排序效果与其相应的C++内存屏障兼容VarHandle

  • #fullFence兼容于atomic_thread_fence(memory_order_seq_cst)
  • #acquireFence兼容于atomic_thread_fence(memory_order_acquire)
  • #releaseFence兼容于atomic_thread_fence(memory_order_release)
  • #loadLoadFence并且没有兼容的C++计数器部件#storeStoreFence

兼容”这个词在这里似乎非常重要,因为当涉及到细节时,语义明显不同。例如,所有C++障碍都是双向的,而Java的障碍不是(必然的)。

  1. 大多数内存屏障也具有同步效果。这些尤其取决于所使用的屏障类型和其他线程中先前执行的屏障指令。由于屏障指令的全部含义是特定于硬件的,因此我将坚持使用更高级别(C++)屏障。例如,在C++中,在发布障碍指令之前所做的更改对于执行获取障碍指令的线程是可见的。

我的假设是否正确?如果是这样,我得出的问题是:

  1. 中可用的内存屏障是否会导致任何类型的内存同步?VarHandle

  2. 无论它们是否会导致内存同步,在Java中重新排序约束可能有什么用处?Java内存模型已经提供了一些关于在涉及易失性字段,锁或类似操作时进行排序的非常强大的保证。VarHandle#compareAndSet

如果你正在寻找一个例子:前面提到的,一个内部类(上面链接的源代码),在第1079行中建立了一个完整的围栏,函数。但是,我不清楚它的作用。BufferedSubscriptionSubmissionPublishergrowAndAdd


答案 1

这主要是一个非答案,真的(最初想把它作为一个评论,但正如你所看到的,它太长了)。只是我自己经常质疑这一点,做了很多阅读和研究,在这个时间点上我可以肯定地说:这很复杂。我甚至用jcstress编写了多个测试来弄清楚它们是如何工作的(同时查看生成的汇编代码),虽然其中一些测试在某种程度上是有意义的,但总的来说,这个主题绝不容易。

您需要了解的第一件事:

Java语言规范(JLS)在任何地方都没有提到障碍。对于java来说,这将是一个实现细节:它实际上是在语义之前发生。为了能够根据JMM(Java内存模型)正确指定这些,JMM必须进行大量更改

这是一项正在进行的工作。

其次,如果你真的想在这里划伤表面,这是首先要注意的事情。谈话令人难以置信。我最喜欢的部分是当赫伯·萨特举起他的5根手指说:“这就是有多少人可以真正和正确地使用这些。这应该给你一个涉及的复杂性的提示。尽管如此,还是有一些微不足道的例子很容易掌握(比如由多个线程更新的计数器,它不关心其他内存保证,而只关心它本身是否正确递增)。

另一个例子是,当(在java中)你想要一个标志来控制线程停止/启动。你知道,经典的:volatile

volatile boolean stop = false; // on thread writes, one thread reads this    

如果您使用java,您将知道如果没有此代码就会被破坏(例如,您可以阅读为什么没有它,双重检查锁定会被破坏)。但是你也知道,对于一些编写高性能代码的人来说,这太过分了吗? 读/写还保证了顺序的一致性 - 这有一些很强的保证,有些人想要一个较弱的版本。volatilevolatile

线程安全标志,但不是易失性的?是的,没错:.VarHandle::set/getOpaque

你会问为什么有人可能需要它?并非每个人都对 .volatile

让我们看看如何在java中实现这一点。首先,API中已经存在这种奇特的东西:.这在Java内存模型中没有指定,也没有明确的定义;仍然有人使用它(LMAX,afaik或这个来阅读更多)。恕我直言,是(或)。AtomicInteger::lazySetAtomicInteger::lazySetVarHandle::releaseFenceVarHandle::storeStoreFence


让我们试着回答为什么有人需要这些

JMM 基本上有两种访问字段的方法:普通易失性(这保证了顺序的一致性)。您提到的所有这些方法都存在,以在这两者之间带来一些东西 - 释放/获取语义;有些情况下,我猜,人们实际上需要这个。

释放/获取的更多放松是不透明的我仍然试图完全理解


因此,底线(你的理解是相当正确的,顺便说一句):如果你打算在java中使用它 - 他们目前没有规范,请自行承担风险。如果您确实想了解它们,它们C++等效模式是开始的地方。


答案 2

推荐