3

请参阅此博客和此主题

似乎即使在单线程中代码也会重新排序?

public int hashCode() {
 if (hash == 0) { // (1)
     int off = offset;
     char val[] = value;
     int len = count;

     int h = 0;
     for (int i = 0; i < len; i++) {
         h = 31*h + val[off++];
     }
     hash = h;
  }
  return hash; // (2)
}

但这对我来说真的很困惑,为什么 (2) 可以返回 0 而 (1) 可以是非零?

如果我在单线程中使用代码,这甚至都行不通,怎么会发生?

4

2 回答 2

1

java内存模型的第一点是:

线程中的每个动作都发生在该线程中的每个动作之前,该线程中的每个动作都按程序顺序进行。

这就是为什么在单线程中重新排序是不可能的。只要代码不同步,就不会为多个线程提供此类保证。

看看 String hashCode 的实现。它首先将哈希加载到局部变量,然后才执行检查和返回。这就是防止这种重新排序的方式。但这并不能让我们免于多次 hashCode 计算。

于 2013-05-30T05:59:12.877 回答
0

第一个问题:

指令的重新排序会在单线程执行中发生吗?

回答:

指令的重新排序是一种编译器优化。无论涉及多少个线程,一个线程中的指令顺序都是相同的。或者:在单线程中也是。

第二个问题:

为什么这会导致多线程出现问题,但不会导致单线程出现问题?

回答:

这种重新排序的规则旨在保证在单线程或正确同步的代码中没有奇怪的影响。这意味着:如果我们编写的代码既不是单线程也不是正确同步的,可能会出现奇怪的影响,我们必须了解规则并注意避免这些影响。

因此,正如原始博客的作者所说:如果您不确定是否了解这些规则,请不要尝试。并且每个编译器都将经过测试以不破坏 String.hashCode() 但不会使用您的代码测试编译器。

编辑: 第三个问题:

再一次,到底发生了什么?

回答:

当我们查看代码时,它会很好地处理看不到另一个线程的变化。所以我们首先要了解的是:方法不返回变量、常量或文字。当程序计数器重置时,没有方法返回堆栈顶部的内容。这必须在某个时间点初始化,以后可以覆盖。这意味着它可以首先使用hash(0 now) 的内容进行初始化,然后另一个线程完成计算并设置hash为某个值,然后进行检查hash == 0。反过来,返回值不再被覆盖,而是返回 0。

所以重点是:返回值可以独立于返回的变量而改变,因为它不一样。现代编程语言让它看起来一样,让我们​​的生活更轻松。但是当你不遵守规则时,这个抽象是整体的。

于 2013-05-29T16:14:51.987 回答