VarHandle get / setOpaque

时间:2019-05-28 12:19:08

标签: java jvm-hotspot memory-barriers java-12

我一直在努力了解VarHandle::setOpaqueVarHandle::getOpaque确实正在做什么。到目前为止,这并不是一件容易的事-我认为有一些事情(但是我不会在问题本身中提出它们,而不是为了弄糊涂),但是总的来说,这充其量只能说是误导对我来说。

文档:

  

返回按程序顺序访问的变量的值...

请问我是否知道:

int xx = x; // read x
int yy = y; // read y

这些读取可以重新排序。另一方面,如果我有:

// simplified code, does not compile, but reads happen on the same "this" for example
int xx = VarHandle_X.getOpaque(x); 
int yy = VarHandle_Y.getOpaque(y);

这次不能重新订购吗?这就是“程序顺序”的意思吗?我们是否在谈论在这里插入障碍物以禁止这种重新排序?如果是这样,由于这是两个负载,是否会达到相同的效果?通过:

 int xx = x;
 VarHandle.loadLoadFence()
 int yy = y;

但这变得更加棘手:

  

...但是不能保证相对于其他线程的内存排序效果。

我无法拿出一个例子来假装我理解这部分。

在我看来,本文档针对的是那些确切知道自己在做什么的人(而我绝对不是一个人)...那么有人可以在这里阐明一下吗?

2 个答案:

答案 0 :(得分:3)

  

请问我是否知道:

int xx = x; // read x
int yy = y; // read y
     

这些读物可以重新排序。

这些读取可能不仅碰巧被重新排序,而且可能根本不发生。线程可能会为x和/或y使用先前读取的旧值,或者先前已写入这些变量的值,而实际上,可能尚未执行写入操作,因此“读取线程”可能使用值,那时其他线程可能不知道并且不在堆内存中(也许永远不会)。

  

另一方面,如果我有:

// simplified code, does not compile, but reads happen on the same "this" for example
int xx = VarHandle_X.getOpaque(x); 
int yy = VarHandle_Y.getOpaque(y);
     

这次不能重新订购吗?这就是“程序顺序”的意思吗?

简单地说,不透明读写的主要特征是它们实际上会发生。这意味着它们不能相对于至少具有相同强度的其他内存访问进行重新排序,但这对普通的读写没有影响。

program order由JLS定义:

  

t 程序顺序是一个总顺序,反映了根据 t的线程内语义执行这些动作的顺序。

这是为表达式和语句指定的evaluation order。只要只涉及一个线程,我们感知效果的顺序。

  

我们是否在谈论在这里插入障碍物以禁止这种重新排序?

不,没有障碍,这可能是短语“ …但不能保证相对于其他线程的内存排序效果”的意图。

也许,我们可以说不透明访问的作用有点像volatile在Java 5之前,它强制读取访问以查看最新的堆内存值(只有在写端也使用不透明或甚至更强的模式),但不会影响其他读写操作。

那你怎么办呢?

一个典型的用例是取消或中断标志,该标志不应该建立“先发生”关系。通常,已停止的后台任务对在发出信号之前已停止的任务所感知的动作没有兴趣,但是只会结束其自身的活动。因此,以不透明模式写入和读取标志足以确保最终注意到信号(与正常访问模式不同),但不会对性能造成任何其他负面影响。

同样,后台任务可以编写进度更新(如百分比数),报告线程应该及时注意到该进度更新,而在发布之前不需要 happens-before 关系。最终结果。

如果您只希望对longdouble进行原子访问,而没有任何其他影响,这也很有用。

由于使用final字段的真正不可变对象不受数据争夺的影响,因此您可以使用不透明模式及时发布不可变对象,而不会产生发布/获取模式发布的广泛影响。

一种特殊情况是定期检查状态以获取期望值更新,并在可用时以更强的模式查询该值(或显式执行匹配的fence指令)。原则上,无论如何都只能在写入和后续读取之间建立先发生关系,但是由于优化程序通常没有能力识别这种线程间用例,因此性能至关重要代码可以使用不透明访问来优化这种情况。

答案 1 :(得分:0)

不透明意味着执行不透明操作的线程可以按照程序顺序观察其自身的动作,仅此而已。

其他线程可以自由观察任何顺序的线程动作。在x86上,由于它具有

  

写入顺序与存储缓冲区转发

内存模型,因此即使线程在加载前确实存储了。可以将存储缓存在存储缓冲区中,并且在任何其他内核上执行的某些线程将以相反的顺序观察线程操作,而不是按存储加载。因此,不透明的操作是免费在x86上完成的(在x86上我们实际上也是免费获取的,有关其他一些体系结构及其内存模型的详细信息,请参见此极为详尽的答案:https://stackoverflow.com/a/55741922/8990329

为什么有用?好吧,我可以推测,如果某个线程观察到以不透明内存语义存储的值,那么随后的读取将观察到“至少这个或以后的值”(普通内存访问不提供这种保证,对吗?)。

此外,由于Java 9 VarHandles与C语言中的获取/释放/使用语义有关,所以我认为值得注意的是,不透明访问类似于标准中定义的memory_order_relaxed,如下所示:

  

对于memory_order_relaxed,没有任何操作命令存储。

提供一些示例。