使用常规HashMap双重检查锁定

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

 class Test { private final Map map = new HashMap(); public B fetch(A key, Function 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块中完成,代码不使用迭代器,因此从技术上讲,应该没有“并发修改”问题。

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

更新2016-12-30

这个问题通过Holger下面的评论回答“ HashMap.get不会修改结构,但是你的put调用确实如此。因为在synchronized块之外有一个get的调用,它可以看到一个put的不完整状态行动同时发生。“ 谢谢!

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

如果只从一个线程调用此代码,那么你就太复杂了; 你不需要任何同步。 但显然这不是你的意图。

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

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

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