从Unsafe.putOrdered*()实现发布的获取?

Dim*_*rov 8 java concurrency multithreading memory-model memory-barriers

您认为在Java中实现发布/获取对的获取部分的最佳方法是什么?

我正在尝试使用经典的发布/获取语义模拟我的应用程序中的一些操作(没有StoreLoad和没有跨线程的顺序一致性).

有几种方法可以实现JDK中存储释放的粗略等效.java.util.concurrent.Atomic*.lazySet()并且sun.misc.Unsafe.putOrdered*()最常被引用的方法是做到这一点.然而,没有明显的方法来实现负载获取.

  • JDK API主要允许在内部lazySet()使用volatile变量,因此它们的存储版本与易失性负载配对.理论上,易失性负载应该比负载获取更昂贵,并且在前面的存储释放的上下文中不应该提供比纯粹的负载获取更多的东西.

  • sun.misc.Unsafe虽然这些获取方法是针对即将推出的VarHandles API计划的,但并未提供方法的getAcquire()*等效putOrdered*()方法.

  • 听起来像它会起作用的东西是明显的负荷,接着是sun.misc.Unsafe.loadFence().有点令人不安的是,我还没有在其他任何地方看到过这种情况.这可能与它是一个非常丑陋的黑客的事实有关.

PS我很清楚JMM没有涵盖这些机制,它们不足以维持顺序一致性,并且它们创建的动作不是同步动作(例如我理解它们例如打破了IRIW).我也理解,提供的商店版本Atomic*/Unsafe通常用于急切地将引用或生产者/消费者场景中的空白作为一些重要索引的优化消息传递机制.

qww*_*sad 5

易失性读取正是您所需要的.

事实上,相应的易失性操作已经具有释放/获取语义(否则发生 - 在配对的易失写入读取之前不可能发生),但成对的易失性操作不仅应该是顺序一致的(〜之前发生),而且它们应该是在总同步顺序中,这就是为什么StoreLoad在volatile写入之后插入barrier:为了保证向不同位置的volatile写入的总顺序,所以所有线程将以相同的顺序看到这些值.

易失性读取具有获取语义:来自热点代码库的证明,还有Doug Lea在JSR-133 cookbook中的直接推荐(LoadLoad以及LoadStore每次易失性读取后的障碍).

Unsafe.loadFence()也有获取语义(证明),但用于不读取值(你可以用普通易失性读取做同样的事情),但是要防止重新排序普通读取和随后的易失性读取.这在StampedLock中用于乐观阅读(参见StampedLock#validate方法实现和用法).

在评论中讨论后更新.

让我们检查是否Unsafe#loadStore()和volatile读取相同并且具有获取语义.

我正在查看热点C1编译器源代码,以避免读取C2中的所有优化.它将字节码(实际上不是字节码,但其解释器表示)转换为LIR(低级中间表示),然后将图转换为实际操作码取决于目标微体系结构.

Unsafe#loadFence是具有别名的内在_loadFence.在C1 LIR发生器中,它生成:

case vmIntrinsics::_loadFence :
if (os::is_MP()) __ membar_acquire();
Run Code Online (Sandbox Code Playgroud)

__LIR生成的宏在哪里.

现在让我们看看同一个LIR发生器中的易失性读取实现.它尝试插入空检查,检查IRIW,检查我们是否在x32上并尝试读取64位值(使用SSE/FPU制作一些魔法),最后,引导我们使用相同的代码:

if (is_volatile && os::is_MP()) {
    __ membar_acquire();
}
Run Code Online (Sandbox Code Playgroud)

然后,汇编器生成器在此处插入特定于平台的获取指令.

看一下具体的实现(这里没有链接,但是所有都可以在src/cpu/{$ cpu_model}/vm/c1_LIRAssembler _ {$ cpu_model} .cpp中找到)

结论

易失性读取Unsafe#loadFence()在内存排序方面是相同的(但可能不是在可能的编译器优化方面),在大多数流行的x86上它是无操作的,而PowerPC是唯一支持的架构,没有精确的获取障碍.