我一直在努力了解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;
但这变得更加棘手:
我什至无法拿出一个例子来假装我理解这部分。
在我看来,该文档针对的是确切知道他们在做什么的人(我绝对不是一个人)...那么有人可以在这里阐明一些信息吗?
最佳答案
这些读取可能不仅碰巧会被重新排序,而且可能根本不会发生。线程可以使用旧的,以前读取的x
和/或y
值,或者之前已写入这些变量的值,而实际上,可能尚未执行写入操作,因此“读取线程”可以使用值,而不是那时其他线程可能知道并且不在堆内存中(可能永远不会)。
简而言之,不透明读写的主要特征是它们实际上会发生。这意味着它们不能相对于至少具有相同强度的其他内存访问进行重新排序,但这对普通的读取和写入没有影响。
术语program order由JLS定义:
这就是为表达式和语句指定的evaluation order。只要只涉及一个线程,我们感知效果的顺序。
不,没有障碍,这可能是短语“……但不能保证相对于其他线程的内存排序效果”背后的意图。
也许我们可以说不透明访问的作用有点类似于volatile
在Java 5之前,它强制读取访问以查看最新的堆内存值(只有在写端也使用不透明或更高强度的模式时才有意义),但是对其他读取或写入没有影响。
那你该怎么办呢?
典型的用例是不应该建立先发生后关系的取消或中断标志。通常,已停止的后台任务对在发出信号之前已停止的任务所感知的 Action 没有兴趣,但是只会结束其自身的 Activity 。因此,以不透明模式写入和读取标志足以确保最终注意到信号(与正常访问模式不同),但不会对性能产生任何其他负面影响。
同样,后台任务可以编写进度更新(如百分比数),报告线程应该及时注意到该进度更新,而在最终结果发布之前不需要事前发生关系。
如果您只希望对long
和double
进行原子访问,而又没有任何其他影响,那么它也很有用。
由于使用final
字段的真正不可变对象(immutable对象)不受数据争用的影响,因此您可以使用不透明模式及时发布不可变对象(immutable对象),而不会产生发布/获取模式发布的广泛影响。
一种特殊情况是定期检查状态以获取期望值更新,并且一旦可用就用更强的模式查询该值(或显式执行匹配的fence指令)。原则上,无论如何都只能在写入与后续读取之间建立事前发生的关系,但是由于优化程序通常没有能力识别这种线程间用例,因此性能关键代码可以使用不透明访问来进行优化这样的情况。