在Java中模拟内存屏障以摆脱易失性读取

syd*_*nal 7 java memory concurrency volatile

假设我有一个并发访问的字段,它被读取多次且很少写入。

public Object myRef = new Object();
Run Code Online (Sandbox Code Playgroud)

假设一个线程T1将一分钟一次将myRef设置为另一个值,而其他N个线程将连续并发读取myRef数十亿次。我只需要myRef最终对所有线程可见。

一个简单的解决方案是使用AtomicReference或简单的volatile,如下所示:

public volatile Object myRef = new Object();
Run Code Online (Sandbox Code Playgroud)

但是,afaik易失性读取确实会导致性能损失。我知道这是微不足道的,这更像是我想知道的东西,而不是我真正需要的东西。因此,让我们不必担心性能,并假设这是一个纯粹的理论问题。

因此,问题归结为:是否有办法通过在写站点进行某些操作来安全地绕过那些很少被写入的引用的易失性读取?

经过一番阅读后,看来内存障碍可能是我所需要的。因此,如果存在这样的构造,我的问题将得到解决:

  • 调用屏障(同步)
  • 一切都已同步,所有线程将看到新值。(在读取的站点上没有永久性的开销,在缓存同步时,它可以是陈旧的或一次性花费,但是此后,它又回到常规字段,直到下一次写入为止)。

在Java中或一般情况下有这样的构造吗?在这一点上,我忍不住想想,如果存在类似的东西,那么聪明的维护它们的人已经将其合并到原子包中了。(不是经常需要频繁读写的情况吗?)那么也许我的想法出了点问题,这样的构造根本不可能吗?

我已经看到一些代码示例出于类似的目的而使用了“ volatile”,从而利用了合约之前发生的情况。有一个单独的同步字段,例如:

public Object myRef = new Object();
public volatile int sync = 0;
Run Code Online (Sandbox Code Playgroud)

并在编写线程/站点时:

myRef = new Object();
sync += 1 //volatile write to emulate barrier
Run Code Online (Sandbox Code Playgroud)

我不确定这是否可行,有人认为这仅适用于x86体系结构。在阅读了JMS中的相关章节之后,我认为只有在该易失性写入与需要查看myRef新值的线程中进行易失性读取相结合的情况下,才能保证工作正常。(因此,不能摆脱易失性读取)。

回到我原来的问题;这有可能吗?Java可能吗?Java 9 VarHandles中的新API之一是否可行?

Ste*_*n C 2

所以基本上你想要 a 的语义volatile而不需要运行时成本。

我认为这是不可能的。

问题在于,运行时成本volatile是由于编写器和读取器代码中实现内存屏障的指令造成的。如果您通过消除读取器的内存障碍来“优化”读取器,那么您将不再保证读取器在实际写入时会看到“很少写入”的新值。

FWIW,该类的某些版本sun.misc.Unsafe提供了显式的loadFence,storeFencefullFence方法,但我认为使用它们不会比使用volatile.


假设...

您想要的是多处理器系统中的一个处理器能够告诉所有其他处理器:

“嘿!无论你在做什么,都要使地址 XYZ 的内存缓存无效,然后立即执行。”

不幸的是,现代 ISA 不支持这一点。

实际上,每个处理器控制自己的缓存。