如何理解JDK9内存模型?

How to understand JDK9 memory model?

我正在学习 JDK9 内存模型。

看完演讲 Java Memory Model Unlearning Experience 和阅读论文 Using JDK 9 Memory Order Modes.

我对一些概念感到困惑。

  1. 不透明是否立即保证可见性

  2. 如何理解论文中的偏序全序

第一个问题,论文说

It is almost never a good idea to use bare spins waiting for values of variables. Use Thread.onSpinWait, Thread.yield, and/or blocking synchronization to better cope with the fact that "eventually" can be a long time, especially when there are more threads than cores on a system.

所以如果我写代码:

// shared variable I and VarHandle I_HANDLE which referred to I
public static int I = 0;

public static final VarHandle I_HANDLE;

// Thread-1
I_HANDLE.setOpaque(1);

// Thread-2
while((int) I_HANDLE.getOpaque() == 0){
}

线程 2 最终会终止,但可能会在很长时间后终止?

如果是这样,是否有任何最小的方法来保证线程 2 立即看到线程 1 的修改? (Release/Acquire?不稳定?)

没有“立即”更新之类的东西。甚至电也以有限的速度移动。通常,要求在特定时间跨度内的可感知效果就像要求操作的特定执行时间。两者都不能保证,因为它们是 JVM 无法更改的底层架构的属性。

实际上,当然,JVM 开发人员试图使操作尽可能快,作为程序员,对您来说重要的是,关于更新的线程间可见性,没有比不透明写入更快的替代方法。更强的访问模式不会改变更新变得可见的速度,它们会为读取和写入的重新排序添加额外的限制。

因此,在您的示例中,更新将在体系结构和系统负载允许的情况下尽快显示1,但不要询问实际数字。没有人能说这需要多长时间。如果您需要时间量方面的保证,则需要一个特殊的(“实时”)实现,它可以为您提供 Java 内存模型之外的额外保证。


1举一个实际场景:线程1和2可能会竞争同一个CPU。线程 1 写入值并在任务切换之前继续 运行 操作系统特定时间(甚至不能保证线程 2 是下一个)。这意味着可能会经过相当长的时间,无论是挂钟时间还是写入后线程 1 的进度。当然,其他线程也可能同时在其他 CPU 核心上取得很大进展。但也有可能线程 2 在线程 1 提交写入之前进行轮询是线程 1 没有机会写入新值的原因。这就是为什么你应该用 onSpinWaityield 标记这样的轮询循环,让执行环境有机会防止这种情况发生。有关两者之间差异的讨论,请参阅

简单来说,opaque就是要进行读写操作。所以它没有被编译器优化掉。

它不提供任何关于其他变量的排序保证。

因此,这对于性能计数器来说非常有用,其中 1 个线程执行更新,而其他线程读取它。

但是如果你会做以下(伪)

// global
final IntReference a = new IntReference();
final IntReference b = new IntReference();

void thread1(){
    a.setPlain(1);
    b.setOpaque(1);
}

void thread2(){
    int r1 = b.getOpaque();
    int r2 = a.getPlain();
    if(r1 == 1 && r2 == 0) println("violation");
}

那么打印 'violation' 可能是因为:

  • a,b 的商店被重新排序
  • 来自 a 和 b 的负载被重新排序。

但是,如果您使用商店发布和加载获取,则不会发生重新排序,因为发布和获取提供了关于其他变量的排序约束。

void thread1(){
    a.setPlain(1);
    [StoreStore] <--
    [LoadStore]
    b.setRelease(1);
}

void thread2(){
    int r1 = b.getAcquire();
    [LoadLoad] <---
    [LoadStore]
    int r2 = a.getPlain();
    if(r1 == 1 && r2 == 0) println("violation");
}