【问题标题】:Thread-safe lazy initialization线程安全的延迟初始化
【发布时间】:2016-01-27 18:50:27
【问题描述】:

我已阅读有关线程安全延迟初始化的内容,并查看了 String 类中 hashCode 方法的实现。显然这种方法是线程安全的,我为另一个类(不可变)制作了自己的版本。

private int hashcode;

@Override
public int hashCode() {
    int h = hashcode;
    if (h == 0 && array.length > 0) {
        hashcode = (h = Arrays.hashCode(array));
    }
    return h;
}

我的问题是:它真的是线程安全的吗?我不明白为什么。 我看不出是什么阻止了一个线程进入该方法,而另一个线程仍在其中,但也许它弄错了。

【问题讨论】:

  • 线程安全不等同于互斥。这并不意味着一次只有一个线程可以执行该方法。这意味着,无论有多少线程调用该方法,无论以何种顺序,同时或不并发,它们都会得到正确的结果,并使对象处于正确的状态。该方法的线程安全主要取决于数组的不变性。
  • 好的,谢谢。我想我现在明白了,可能发生的更糟糕的是哈希码被计算了几次。但这并不危险,因为对象是不可变的,哈希码总是相同的。
  • 实际上并没有更糟。更糟糕的是 Arrays.hashCode() 返回 0,这将导致 hashCode()每次都被重新计算。此问题已被用作一种通过提交已知具有 0 hashCode 的长字符串来创建巧妙 DDOS 攻击的方法。
  • 我应该添加一个条件来防止这种情况发生吗?
  • 布尔值不允许在没有某种同步的情况下自动设置 hashCode:一个线程会切换布尔值并设置 hashCode,另一个线程可以看到切换后的布尔值,但看不到正确的 hashCode .

标签: java thread-safety lazy-initialization


【解决方案1】:

您看到的代码可能效率低下。可能发生的情况是多个线程同时进入hashCode() 函数,它们都计算哈希码,而不是其中一个计算哈希码,其他线程等待结果。

因为String 是不可变的,所以这不是问题。如果对象是可变的,则需要在其hashCode() 函数中进行同步(因为对象的状态可以在hashCode() 内部更改。

【讨论】:

  • 哦对了,我忘了说我的对象是不可变的
【解决方案2】:

正如@JB Nizet 指出的那样,主要问题是您可能有一个非空数组,其哈希恰好是0。你需要能够区分“Hash is really 0”和“Hash is unknown”。您可以为此使用可为空的 Integer

private final AtomicReference<Integer> hashcode = new AtomicReference<>();

@Override
public int hashCode() {
    Integer h = hashcode.get();
    if (h != null) return h;
    int computedHash = Arrays.hashCode(array);
    hashcode.compareAndSet(null, computedHash);
    return computedHash;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-11-17
    • 2015-07-27
    • 2014-07-11
    • 1970-01-01
    • 1970-01-01
    • 2012-03-16
    • 2020-05-10
    • 2020-02-09
    相关资源
    最近更新 更多