如何理解JDK9内存模型?
How to understand JDK9 memory model?
我正在学习 JDK9 内存模型。
看完演讲
Java Memory Model Unlearning Experience
和阅读论文
Using JDK 9 Memory Order Modes.
我对一些概念感到困惑。
不透明是否立即保证可见性?
如何理解论文中的偏序和全序?
第一个问题,论文说
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 没有机会写入新值的原因。这就是为什么你应该用 onSpinWait
或 yield
标记这样的轮询循环,让执行环境有机会防止这种情况发生。有关两者之间差异的讨论,请参阅 。
简单来说,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");
}
我正在学习 JDK9 内存模型。
看完演讲 Java Memory Model Unlearning Experience 和阅读论文 Using JDK 9 Memory Order Modes.
我对一些概念感到困惑。
不透明是否立即保证可见性?
如何理解论文中的偏序和全序?
第一个问题,论文说
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 没有机会写入新值的原因。这就是为什么你应该用 onSpinWait
或 yield
标记这样的轮询循环,让执行环境有机会防止这种情况发生。有关两者之间差异的讨论,请参阅
简单来说,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");
}