回到并发。现在很明显,要使双重检查锁定
起作用,需要将变量声明为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 有助于 HashMap
与 ConcurrentHashMap
主题。
更新 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/