用常规的HashMap重复检查锁定 [英] Double checked locking with regular HashMap

查看:197
本文介绍了用常规的HashMap重复检查锁定的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

返回并发。现在很明显,对于双重检查锁定来说,变量需要被声明为 volatile 。但是,如果使用双重检查锁定,如下所示。

  class Test< A,B> {

私人最终地图< 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);
}
}
}
返回值;
}

}

为什么它真的必须是一个 ConcurrentHashMap ,而不是一个普通的 HashMap ?所有的map修改都在 synchronized 块中完成,并且代码不使用迭代器,所以技术上不应该存在并发修改问题。



请避免建议使用 putIfAbsent / computeIfAbsent ,因为我在询问概念,而不是使用API​​ :)除非使用此API贡献给 HashMap vs ConcurrentHashMap 主题。

更新2016-12-30



此问题已由Holger> HashMap.get 不会修改结构,但是您调用 put 会发生。是在同步块之外调用 get ,它可以看到 put 操作的不完全状态同时发生。 谢谢!

解决方案

这个问题在很多方面都很混乱,很难回答。

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



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



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

Back to concurrency. By now it is clear that for the double checked locking to work the variable needs to be declared as volatile. But then what if double checked locking is used as below.

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;
    }

}

Why does it really have to be a ConcurrentHashMap and not a regular HashMap? All map modification is done within the synchronized block and the code doesn't use iterators so technically there should be no "concurrent modification" problems.

Please avoid suggesting the use of putIfAbsent/computeIfAbsent as I am asking about the concept and not the use of API :) unless using this API contributes to HashMap vs ConcurrentHashMap subject.

Update 2016-12-30

This question was answered by a comment below by Holger "HashMap.get doesn’t modify the structure, but your invocation of put does. Since there is an invocation of get outside of the synchronized block, it can see an incomplete state of a put operation happening concurrently." Thanks!

解决方案

This question is muddled on so many counts that its hard to answer.

If this code is only ever called from a single thread, then you're making it too complicated; you don't need any synchronization. But clearly that's not your intention.

So, multiple threads will call the fetch method, which delegates to HashMap.get() without any synchronization. HashMap is not thread-safe. Bam, end of story. Doesn't even matter if you're trying to simulate double-checked locking; the reality is that calling get() and put() on a map will manipulate the internal mutable data structures of the HashMap, without consistent synchronization on all code paths, and since you can be calling these concurrently from multiple threads, you're already dead.

(Also, you probably think that HashMap.get() is a pure read operation, but that's wrong too. What if the HashMap is actually a LinkedHashMap (which is a subclass of HashMap.) LinkedHashMap.get() will update the access order, which involves writing to internal data structures -- here, concurrently without synchronization. But even if get() is doing no writing, your code here is still broken.)

Rule of thumb: when you think you have a clever trick that lets you avoid synchronizing, you're almost certainly wrong.

这篇关于用常规的HashMap重复检查锁定的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆