在Does a correctly synchronized program still allow data race?(Part I)的讨论中,我们得到了两个非常好的例子。
我只想讨论第二个。为了方便起见,我在这里仅举第二个例子:
public int hashCode() {
if (hash == 0 && count > 0) { //(1)
int h = hash;
int off = offset;
char val[] = value;
int len = count;
for (int i = 0; i < len; i++) {
h = 31*h + val[off++];
}
hash = h; //(2)
}
return hash; //(3)
}
根据先发生关系定义中的第一项:如果x和y是同一线程的动作,并且x按程序顺序位于y之前,则hb(x,y),我们可能得出以下结论:
有hb((1),(2))和hb((2),(3)),因此hb((1),(3))。
因为:
hash是一个共享变量;
(1),(2),(3)是同一线程的所有动作;
(1)位于(2)之前,(2)位于(3)在程序顺序之前。
现在回到我的问题:如果(1)和(3)之间存在先发生的关系,则(1)和(3)永远不应重新排序。
我的解释中有什么误会吗?您的意见呢?
最佳答案
从不同线程提交的(1),(2)和(3)产生的动作没有顺序。根据JLS 17.4.5,唯一的事情是在排序之前发生在同一线程提交的(1),(2),(3)的三元组中:
如果x和y是同一线程的动作,并且x按程序顺序位于y之前,则hb(x,y)。
只要执行的线程没有发现与程序顺序不一致的顺序,就可以在任何执行中对机器指令进行重新排序。对于任何其他线程,由于没有发生任何事情,它们可以按任何顺序观察动作(甚至根本不观察它们),然后再从这些动作转到其他线程的任何动作。但是,您的示例仅包含一次写入,并且至少必须进行两次写入才能使“乱序”的概念有意义。仅写操作具有其他线程可观察到的效果。