从Unsafe.putOrdered *()实现版本的获取?

时间:2016-05-08 22:14:31

标签: java multithreading concurrency memory-barriers memory-model

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

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

有几种方法可以在JDK中实现商店版本的粗略等效。 java.util.concurrent.Atomic*.lazySet()和基础sun.misc.Unsafe.putOrdered*()是最常被提及的方法。但是,没有明显的方法来实现负载获取。

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

  • sun.misc.Unsafe未提供getAcquire()*putOrdered*()方法,即使为即将推出的VarHandles API计划了此类获取方法。

  • 听起来像它可以工作的东西是一个普通的负载,然后是sun.misc.Unsafe.loadFence()。我在其他任何地方都没有看到这种情况,这有点令人不安。这可能与它是一个相当丑陋的黑客有关。

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

2 个答案:

答案 0 :(得分:5)

易失性读取正是您所寻找的。

实际上,相应的易失性操作已经具有释放/获取语义(否则发生在配对易失性写入读取之前是不可能的),但成对的易失性操作不仅应该是顺序一致的(〜之前发生),而且它们应该在total synchronization order中,这就是在volatile写入之后插入StoreLoad屏障的原因:为了保证不同位置的易失性写入的总顺序,所以所有线程都会以相同的顺序看到这些值。

易失性读取从热点代码库获取语义:proof,在每次易失性读取后,Doug Lea也会在JSR-133 cookbookLoadLoadLoadStore障碍中直接推荐

Unsafe.loadFence()也具有获取语义(proof),但不用于读取值(您可以对普通易失性读取执行相同操作),但是要防止使用后续易失性读取重新排序普通读取。这在StampedLock中用于乐观阅读(请参阅StampedLock#validate方法实现和用法)。

在评论中讨论后更新。

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

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

Unsafe#loadFenceintrinsic,其中包含_loadFence别名。在C1 LIR generator中,它会生成以下内容:

case vmIntrinsics::_loadFence :
if (os::is_MP()) __ membar_acquire();

其中__是生成LIR的宏。

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

if (is_volatile && os::is_MP()) {
    __ membar_acquire();
}

汇编程序生成器然后插入特定于平台的获取指令here

查看具体实现(此处没有链接,但所有链接都可以在src / cpu / {$ cpu_model} / vm / c1_LIRAssembler _ {$ cpu_model} .cpp中找到)

  • SPARC

    void LIR_Assembler::membar_acquire() {
        // no-op on TSO
    }
    
  • 86

    void LIR_Assembler::membar_acquire() {
        // No x86 machines currently require load fences
    }
    
  • Aarch64(弱存储模型,应该存在障碍)

    void LIR_Assembler::membar_acquire() {
        __ membar(Assembler::LoadLoad|Assembler::LoadStore);
    }
    

    根据无政府主义architecture description,这种membar将在加载后编译为dmb ishld指令。

  • PowerPC(也是弱内存模型)

    void LIR_Assembler::membar_acquire() {
        __ acquire();
    }
    

    然后转换为特定的PowerPC指令lwsync。根据{{​​3}} lwsync在语义上等同于

      

    lwsync订单商店|商店,                           负载|店铺,                           负载|负载,                   但不是Store | Load

    但只要PowerPC没有任何较弱的障碍,这就是在PowerPC上实现获取语义的唯一选择。

<强>结论

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

答案 1 :(得分:1)

根据您的具体要求,执行非易失性加载,可能后跟可能的易失性加载是Java中最好的。

您可以使用

的组合来完成此操作
public static void readData (Scanner inputFile, double [][] payInfo, String [] names) {
     Integer payInfoLenght = payInfo.lenght;
     Integer namesLenght = names.lenght;
     if (MAX_EMPLOY > payInfoLenght || MAX_EMPLOY > namesLenght) {
          System.out.println("Wrong size of tabels");
     } else {
          for (int i = 0; i < MAX_EMPLOY; i++) {
             if (inputFile.hasNextDouble()) {
                 payInfo [i][0] = inputFile.nextDouble();
             }
             if (inputFile.hasNextDouble()) {
                 payInfo [i][1] = inputFile.nextDouble();
             }
             if (inputFile.hasNextLine()) {
                 names [i] = inputFile.nextLine();
             }
          } 
     }
} 

此模式可用于环形缓冲区,以最大限度地减少缓存行的流失。