11

我一直在努力了解什么VarHandle::setOpaque真正VarHandle::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;

但它变得更加棘手:

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

我想不出一个例子来假装我理解这部分。

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

4

2 回答 2

13

我的理解是,如果我有:

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);

这次不能再下单了?这就是“程序顺序”的意思吗?

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

术语程序顺序由 JLS 定义:

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

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

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

不,不涉及任何障碍,这可能是短语“<em>...但不能保证相对于其他线程的内存排序效果”背后的意图。

也许,我们可以说 opaque 访问的工作方式有点像volatileJava 5 之前的情况,强制读取访问以查看最近的堆内存值(这只有在写入端也使用 opaque 或更强大的模式时才有意义),但没有对其他读取或写入的影响。

那么你能用它做什么呢?

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

同样,后台任务可以写入进度更新,例如百分比数字,报告 (UI) 线程应该及时通知,而在发布最终结果之前不需要发生之前的关系。

如果您只想对 and 进行原子访问,而没有任何其他影响,它也很有longdouble

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

一种特殊情况是定期检查预期值更新的状态,一旦可用,就使用更强的模式查询值(或显式执行匹配的栅栏指令)。原则上,happens-before关系只能在写入和其后续读取之间建立,但由于优化器通常无法识别这种线程间用例,因此性能关键代码可以使用不透明访问来优化这样的场景。

于 2019-05-28T14:46:37.960 回答
2

opaque 意味着执行 opaque 操作的线程保证按照程序顺序观察自己的动作,但仅此而已。

其他线程可以任意顺序观察线程动作。在 x86 上这是一个常见的情况,因为它有

使用存储缓冲区转发按顺序写入

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

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

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

对于memory_order_relaxed,没有操作命令内存。

并提供了一些示例。

于 2019-06-05T02:10:40.430 回答