修改后的String.hashCode()的重新排序说明

时间:2013-05-29 11:10:25

标签: java jvm

请参阅此blog和此topic

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

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)可能不为零?

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

2 个答案:

答案 0 :(得分:1)

java memory model的第一点是:

  

线程中的每个操作都发生在该线程中的每个操作之前   在程序的订单后面出现。

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

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

答案 1 :(得分:0)

第一个问题:

是否会在单线程执行中重新排序指令?

答案:

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

第二个问题:

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

答案:

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

原来,原始博客的箴言说:如果你不确定理解这些规则,请不要尝试。并且每个编译器都将进行测试,不会破坏String.hashCode(),但不会使用您的代码测试编译器。

修改 第三个问题:

又一次真正发生了什么?

答案:

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

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