在使用 ConcurrentMap 的 putIfAbsent 之前,是否应该检查映射是否包含Key

我一直在使用Java的ConcurrentMap作为可以从多个线程使用的映射。putIfAbsent 是一个很好的方法,读/写比使用标准映射操作容易得多。我有一些代码看起来像这样:

ConcurrentMap<String, Set<X>> map = new ConcurrentHashMap<String, Set<X>>();

// ...

map.putIfAbsent(name, new HashSet<X>());
map.get(name).add(Y);

可读性方面,这很好,但它确实需要每次都创建一个新的哈希集,即使它已经在地图中。我可以这样写:

if (!map.containsKey(name)) {
    map.putIfAbsent(name, new HashSet<X>());
}
map.get(name).add(Y);

通过此更改,它会失去一些可读性,但不需要每次都创建HashSet。在这种情况下哪个更好?我倾向于支持第一个,因为它更具可读性。第二个会表现得更好,可能更正确。也许有比这两种更好的方法来做到这一点。

以这种方式使用 putIfAbsent 的最佳做法是什么?


答案 1

并发性很难。如果您要为并发映射而不是直接锁定而烦恼,那么不妨去做。事实上,不要做不必要的查找。

Set<X> set = map.get(name);
if (set == null) {
    final Set<X> value = new HashSet<X>();
    set = map.putIfAbsent(name, value);
    if (set == null) {
        set = value;
    }
}

(通常的堆栈溢出免责声明:在我的头顶上。未经测试。未编译。等等)

更新:1.8 添加了默认方法(这有点有趣,因为该实现对于 )。(1.7增加了“钻石运营商”。computeIfAbsentConcurrentMapMapConcurrentMap<>

Set<X> set = map.computeIfAbsent(name, n -> new HashSet<>());

(请注意,您负责 . 中包含的 s 的任何操作的线程安全性。HashSetConcurrentMap


答案 2

就 ConcurrentMap 的 API 用法而言,Tom 的答案是正确的。避免使用 putIfAbsent 的另一种方法是使用 GoogleCollections/Guava MapMaker 中的计算映射,该映射使用提供的函数自动填充值,并为您处理所有线程安全性。它实际上只为每个键创建一个值,如果创建函数很昂贵,则其他线程要求获取相同的键将阻塞,直到该值变为可用。

从Guava 11编辑,MapMaker已被弃用,并被替换为Cache /LocalCache/CacheBuilder的东西。这在用法上有点复杂,但基本上是同构的。


推荐