java - 使用常规 HashMap 双重检查锁定

标签 java concurrency hashmap concurrenthashmap 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?所有 map 修改都在 synchronized block 内完成,并且代码不使用迭代器,因此从技术上讲不应该存在“并发修改”问题。

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

更新 2016-12-30

Holger 在下面的评论中回答了这个问题“HashMap.get 不会修改结构,但是您对 put 的调用会修改。因为有一个调用在同步块(synchronized block)之外的 get 中,它可以看到同时发生的 put 操作的不完整状态。”谢谢!

最佳答案

这个问题在很多方面都很困惑,很难回答。

如果这段代码只被单线程调用,那么你就把它搞得太复杂了;你不需要任何同步。但显然这不是你的意图。

因此,多个线程将调用 fetch 方法,该方法委托(delegate)给 HashMap.get() 而无需任何同步。 HashMap 不是线程安全的。巴姆,故事结束了。如果您尝试模拟双重检查锁定,甚至都没有关系;实际情况是,在 map 上调用 get()put() 将操纵 HashMap 的内部可变数据结构,而没有一致的同步在所有代码路径上,并且由于您可以从多个线程同时调用它们,所以您已经死了。

(此外,您可能认为 HashMap.get() 是纯读取操作,但这也是错误的。如果 HashMap 实际上是 LinkedHashMap(它是 HashMap 的子类)怎么办。 LinkedHashMap.get() 将更新访问顺序,这涉及到写入内部数据结构——在这里,并发地没有同步。但即使 get() 不进行写入,你的代码仍然是错误的。)

经验法则:当您认为自己有一个可以避免同步的聪明技巧时,您几乎可以肯定是错误的。

关于java - 使用常规 HashMap 双重检查锁定,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33350489/

相关文章:

java - 在这种情况下我需要使用线程安全映射吗?

java - 了解 java Stream.limit() 操作

java - Android Studio 2 - 相机安全异常

java - 缓冲区大小如何影响 NIO channel 性能?

c - 我如何让 C 中的多个线程处理二维数组的同一个 for 循环?

android - hashmap 在 simpleadapter 中工作但在自定义数组适配器中不起作用

java - 使用自定义 XML 文件的 Android 滑动选项卡

java - 需要简单解释 "lock striping"如何与 ConcurrentHashMap 一起工作

java - HashMap的equals依赖于EntrySet.keySet吗?

lambda - 使用 HashMap<String, Runnable> 避免重复方法