Java中的内存防护有什么用? [英] What are memory fences used for in Java?

查看:91
本文介绍了Java中的内存防护有什么用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在尝试了解 SubmissionPublisher 的同时( Java SE 10,OpenJDK中的源代码 | docs ),已经实现了在版本9的Java SE中添加的新类,我偶然发现了对 VarHandle 我以前不知道:

Whilst trying to understand how SubmissionPublisher (source code in Java SE 10, OpenJDK | docs), a new class added to the Java SE in version 9, has been implemented, I stumbled across a few API calls to VarHandle I wasn't previously aware of:

fullFence acquireFence releaseFence loadLoadFence storeStoreFence

经过一些研究,尤其是关于内存屏障/栅栏的概念(我之前听说过) ,是的;但是从未使用过它们,因此相当不熟悉它们的语义),我想我对它们的用途有基本的了解。但是,由于我的问题可能是由错误的观念引起的,因此我想确保我一开始就正确:

After doing some research, especially regarding the concept of memory barriers/fences (I have heard of them previously, yes; but never used them, thus was quite unfamiliar with their semantics), I think I have a basic understanding of what they are for. Nonetheless, as my questions might arise from a misconception, I want to ensure that I got it right in the first place:


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

  1. Memory barriers are reordering constraints regarding reading and writing operations.

内存屏障可分为两大类:单向和双向内存屏障,具体取决于它们是否设置限制读或写或两者兼而有之。

Memory barriers can be categorized into two main categories: unidirectional and bidirectional memory barriers, depending on whether they set constraints on either reads or writes or both.

C ++支持各种内存障碍,但是与 VarHandle 提供的内存障碍不匹配。 但是, VarHandle 中提供的一些内存屏障提供了与它们对应的C ++兼容的排序效果

C++ supports a variety of memory barriers, however, these do not match up with those provided by VarHandle. However, some of the memory barriers available in VarHandle provide ordering effects that are compatible to their corresponding C++ memory barriers.


  • #fullFence 与<$ c兼容$ c> atomic_thread_fence(memory_order_seq_cst)

  • #acquireFence atomic_thread_fence兼容(memory_order_acquire)

  • #releaseFence atomic_thread_fence(memory_order_release)

  • #loadLoadFence #storeStoreFence 没有兼容的C ++计数器部分

  • #fullFence is compatible to atomic_thread_fence(memory_order_seq_cst)
  • #acquireFence is compatible to atomic_thread_fence(memory_order_acquire)
  • #releaseFence is compatible to atomic_thread_fence(memory_order_release)
  • #loadLoadFence and #storeStoreFence have no compatible C++ counter part

compatible 一词似乎之所以在这里非常重要,是因为细节之间的语义明显不同。例如,所有C ++障碍都是双向的,而Java障碍不是必需的。

The word compatible seems to really important here since the semantics clearly differ when it comes to the details. For instance, all C++ barriers are bidirectional, whereas Java's barriers aren't (necessarily).


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

  1. Most memory barriers also have synchronization effects. Those especially depend upon the used barrier type and previously-executed barrier instructions in other threads. As the full implications a barrier instruction has is hardware-specific, I'll stick with the higher-level (C++) barriers. In C++, for instance, changes made prior to a release barrier instruction are visible to a thread executing an acquire barrier instruction.

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

Are my assumptions correct? If so, my resulting questions are:


  1. 执行 VarHandle 会导致某种形式的内存同步吗?

  1. Do the memory barriers available in VarHandle cause any kind of memory synchronization?

无论它们是否引起内存同步,重新排序约束在Java中可能有用吗? Java内存模型已经为可变字段,锁或 VarHandle 操作(如 #compareAndSet )进行排序时提供了非常强有力的保证。

Regardless of whether they cause memory synchronization or not, what may reordering constraints be useful for in Java? The Java Memory Model already gives some very strong guarantees regarding ordering when volatile fields, locks or VarHandle operations like #compareAndSet are involved.

如果您正在寻找示例:上述 BufferedSubscription code>是 SubmissionPublisher 的内部类(上面链接的源),在1079行中建立了完整的围栏(函数 growAndAdd ;由于链接的网站不支持片段标识符,因此只需按CTRL + F。但是,我不清楚它的用途。

In case you're looking for an example: The aforementioned BufferedSubscription, an inner class of SubmissionPublisher (source linked above), established a full fence in line 1079 (function growAndAdd; as the linked website doesn't support fragment identifiers, just CTRL+F for it). However, it is unclear for me what it is there for.

推荐答案

这主要是一个无答案的问题,真的(最初是要发表评论,但正如您所看到的,它太长了)。只是我自己问了很多问题,做了很多阅读和研究,因此我可以放心地说:这很复杂。我什至使用 jcstress 编写了多个测试,以弄清它们的工作原理(同时查看生成的汇编代码),尽管其中某些以某种方式是有意义的,但总的来说,这个主题绝非易事。

This is mainly a non-answer, really (initially wanted to make it a comment, but as you can see, it's far too long). It's just that I questioned this myself a lot, did a lot of reading and research and at this point in time I can safely say: this is complicated. I even wrote multiple tests with jcstress to figure out how really they work (while looking at the assembly code generated) and while some of them somehow made sense, the subject in general is by no means easy.

您需要了解的第一件事:

The very first thing you need to understand:


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

The Java Language Specification (JLS) does not mention barriers, anywhere. This, for java, would be an implementation detail: it really acts in terms of happens before semantics. To be able to proper specify these according to the JMM (Java Memory Model), the JMM would have to change quite a lot.

这是正在进行的工作。

This is work in progress.

第二,如果您真的想在此处刮擦表面,请这是第一个值得一看的。谈话真是不可思议。我最喜欢的部分是Herb Sutter举起5根手指说:这是多少人可以真正,正确地使用它们。那应该给您提示所涉及的复杂性。尽管如此,还是有一些容易掌握的琐碎示例(例如,由多个线程更新的计数器并不关心 other 内存保证,而仅关心其本身是否正确递增)。

Second, if you really want to scratch the surface here, this is the very first thing to watch. The talk is incredible. My favorite part is when Herb Sutter raises his 5 fingers and says, "This is how many people can really and correctly work with these." That should give you a hint of the complexity involved. Nevertheless, there are some trivial examples that are easy to grasp (like a counter updated by multiple threads that does not care about other memory guarantees, but only cares that it is itself incremented correctly).

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

Another example is when (in java) you want a volatile flag to control threads to stop/start. You know, the classical:

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

如果使用Java,您会知道 without volatile 这段代码已损坏(例如,您可以阅读为什么没有它就会破坏双重检查锁定的原因)。但是您是否也知道,对于某些编写高性能代码的人来说,这太多了吗? volatile 读/写还保证顺序一致性-具有一些有力的保证,有些人则希望对此有较弱的理解。

If you work with java, you would know that without volatile this code is broken (you can read why double check locking is broken without it for example). But do you also know that for some people that write high performance code this is too much? volatile read/write also guarantees sequential consistency - that has some strong guarantees and some people want a weaker version of this.


线程安全标志,但不是可变的吗?是的,完全是: VarHandle :: set / getOpaque

您会问为什么有人可能需要它?并非每个人都对 volatile 支持的所有更改感兴趣。

And you would question why someone might need that for example? Not everyone is interested with all the changes that are piggy-backed by a volatile.

让我们看看如何在Java中实现这一目标。首先,这些 exotic 东西已经存在于API中: AtomicInteger :: lazySet 。这在Java内存模型中未指定,并且没有明确的定义;仍然有人使用它(LMAX,afaik或以获取更多阅读信息)。恕我直言, AtomicInteger :: lazySet VarHandle :: releaseFence (或 VarHandle :: storeStoreFence )。

Let's see how we will achieve this in java. First of all, such exotic things already existed in the API: AtomicInteger::lazySet. This is unspecified in the Java Memory Model and has no clear definition; still people used it (LMAX, afaik or this for more reading). IMHO, AtomicInteger::lazySet is VarHandle::releaseFence (or VarHandle::storeStoreFence).

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

JMM基本上有两种访问字段的方式: plain volatile (保证顺序一致性) )。您提到的所有这些方法都可以在这两者之间带来一些好处- release / acquire语义;我猜有些情况下人们实际上是需要它们的。

JMM has basically two ways to access a field: plain and volatile (which guarantees sequential consistency). All these methods that you mention are there to bring something in-between these two - release/acquire semantics; there are cases, I guess, where people actually need this.

发布/获取中释放更多甚至会是不透明,其中< a href = https://stackoverflow.com/questions/56342141/varhandle-get-setopaque>我仍在尝试完全理解。

An even more relaxation from release/acquire would be opaque, which I am still trying to fully understand.

因此,底线(您的理解是相当正确的,顺便说一句):如果您打算在Java中使用它-目前尚无规格,风险自负。如果您确实想了解它们,则可以从它们的C ++等效模式开始。

Thus bottom line (your understanding is fairly correct, btw): if you plan to use this in java - they have no specification at the moment, do it on you own risk. If you do want to understand them, their C++ equivalent modes are the place to start.

这篇关于Java中的内存防护有什么用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆