使用常规HashMap双重检查锁定

时间:2015-10-26 16:12:08

标签: java concurrency hashmap concurrenthashmap double-checked-locking

回到并发。到目前为止,很明显,要使double checked locking工作,变量需要声明为volatile。但是,如果使用双重检查锁定,如下所示。

class Test<A, B> {

    private final Map<A, B> map = new HashMap<>();

    public B fetch(A key, Function<A, B> loader) {
        B value = map.get(key);
        if (value == null) {
            synchronized (this) {
                value = map.get(key);
                if (value == null) {
                    value = loader.apply(key);
                    map.put(key, value);
                }
            }
        }
        return value;
    }

}

为什么它必须是 ConcurrentHashMap 而不是常规的 HashMap ?所有映射修改都在synchronized块内完成,代码不使用迭代器,因此从技术上讲,不应该有&#34;并发修改&#34;问题。

请避免建议使用putIfAbsent / computeIfAbsent因为我询问概念而不是API的使用:)除非使用此API有助于{{ 1}} vs HashMap主题。

更新2016-12-30

Holger&#34; ConcurrentHashMap下面的评论回答了这个问题。HashMap.get不修改结构,但是你put的调用确实如此。由于在同步块之外调用了get,因此可以看到同时发生put操作的不完整状态。&#34;谢谢!

1 个答案:

答案 0 :(得分:14)

这个问题混淆了很多,很难回答。

如果只从单个线程调用此代码,那么您将使其过于复杂;你不需要任何同步。但很明显,这不是你的意图。

因此,多个线程将调用fetch方法,该方法在没有任何同步的情况下委托给HashMap.get()。 HashMap不是线程安全的。巴姆,故事的结尾。如果你试图模拟双重检查锁定,那就不重要了;现实情况是,在地图上调用get()put()将操纵HashMap的内部可变数据结构,而不会在所有代码路径上进行一致同步,因为您可以同时调用这些从多个线程,你已经死了。

(另外,您可能认为HashMap.get()是一个纯粹的读取操作,但也是错误的。如果HashMap实际上是一个LinkedHashMap(它是HashMap的子类,那该怎么办。)LinkedHashMap.get ()将更新访问顺序,这涉及写入内部数据结构 - 这里,同时没有同步。但即使get()没有写入,这里的代码仍然被破坏。)

经验法则:当你认为你有一个巧妙的技巧可以让你避免同步时,你几乎肯定是错的。