变量句柄 get/setOpaque

VarHandle get/setOpaque

我一直在努力了解 VarHandle::setOpaqueVarHandle::getOpaque 真正 在做什么。到目前为止,这并不容易 - 我 认为 我得到了一些东西(但不会在问题本身中提出它们,以免混淆水域),但总的来说这是错过了 -对我来说最好的领导。

文档:

Returns the value of a variable, accessed in program order...

据我了解,如果我有:

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

这次不能重新下单了?而这就是"program order"的意思?我们是在谈论在这里插入障碍以禁止这种重新排序吗?如果是这样,因为这是两个负载,是否可以实现相同的目标?通过:

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

但它变得更加棘手:

... but with no assurance of memory ordering effects with respect to other threads.

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

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

Well in my understanding if I have:

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

These reads can be re-ordered.

这些读取可能不仅碰巧被重新排序,它们可能根本不会发生。线程可能会使用以前读取的旧值 x and/or y 或它之前写入这些变量的值,而实际上,写入可能尚未执行,因此“读取线程”可能会使用值,其他线程可能不知道并且当时不在堆内存中(并且可能永远不会)。

On the other had if I have:

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

This time re-orderings are not possible? And this is what it means "program order"?

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

术语 program order 由 JLS 定义:

… the program order of t is a total order that reflects the order in which these actions would be performed according to the intra-thread semantics of t.

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

Are we talking about insertions of barriers here for this re-ordering to be prohibited?

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

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

那么你能用它做什么?

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

同样,后台任务可以写入进度更新,如百分比数字,报告 (UI) 线程应该及时注意到,而没有 happens-before最终结果公布前需要关系

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

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

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

不透明是指执行不透明操作的线程保证按照程序顺序观察自己的动作,仅此而已。

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

write ordered with store-buffer forwarding

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

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

此外,由于 Java 9 VarHandles 在某种程度上与 C 中的 acquire/release/consume 语义相关,我认为值得注意的是,不透明访问类似于 memory_order_relaxed,它在标准中定义如下:

For memory_order_relaxed, no operation orders memory.

提供了一些示例。