在使用ConcurrentMap的putIfAbsent之前,你应该检查地图是否包含Key

Chr*_*ail 69 java concurrency performance concurrenthashmap

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

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

// ...

map.putIfAbsent(name, new HashSet<X>());
map.get(name).add(Y);
Run Code Online (Sandbox Code Playgroud)

可读性明智这很好,但它确实需要每次创建一个新的HashSet,即使它已经在地图中.我可以这样写:

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

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

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

Tom*_*ine 105

并发很难.如果您打算使用并发映射而不是直接锁定,那么您也可以选择它.实际上,不要做超过必要的查找.

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

(通常的stackoverflow免责声明:我的头顶.未经测试.未编译.等等)

更新: 1.8已添加computeIfAbsent默认方法ConcurrentMap(并且Map这有点有趣,因为该实现将是错误的ConcurrentMap).(并且1.7增加了"钻石运营商" <>.)

Set<X> set = map.computeIfAbsent(name, n -> new HashSet<>());
Run Code Online (Sandbox Code Playgroud)

(注意,您负责HashSets中包含的任何操作的线程安全性ConcurrentMap.)

  • +1"并发很难"并使用putIfAbsent的returnvalue (23认同)
  • @zerkms这可能发生,第二个`if(set == null)`用第一个线程通过获胜处理.情况不太可能,额外的分配时间不太可能很大.您可以插入一个临时值,以便为单个线程提供独占访问权限,但这可能会使一般性能和可靠性变得更糟. (2认同)

Jed*_*ith 16

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

从Guava 11 编辑,不推荐使用MapMaker并将其替换为Cache/LocalCache/CacheBuilder.这在使用上稍微复杂一些,但基本上是同构的.


Cra*_*lin 5

您可以使用MutableMap.getIfAbsentPut(K, Function0<? extends V>)Eclipse的集合(前身为GS集合).

调用get(),执行空检查,然后调用的优点putIfAbsent()是我们只计算一次密钥的hashCode,并在哈希表中找到一次正确的位置.在ConcurrentMaps中org.eclipse.collections.impl.map.mutable.ConcurrentHashMap,执行getIfAbsentPut()也是线程安全的和原子的.

import org.eclipse.collections.impl.map.mutable.ConcurrentHashMap;
...
ConcurrentHashMap<String, MyObject> map = new ConcurrentHashMap<>();
map.getIfAbsentPut("key", () -> someExpensiveComputation());
Run Code Online (Sandbox Code Playgroud)

实施org.eclipse.collections.impl.map.mutable.ConcurrentHashMap是真正的非阻塞.虽然我们尽一切努力不会不必要地调用工厂功能,但在争用期间仍有可能不止一次调用它.

这个事实使它与Java 8不同ConcurrentHashMap.computeIfAbsent(K, Function<? super K,? extends V>).此方法的Javadoc指出:

整个方法调用是以原子方式执行的,因此每个键最多应用一次该函数.其他线程在此映射上的某些尝试更新操作可能会在计算过程中被阻止,因此计算应该简短而简单...

注意:我是Eclipse Collections的提交者.

  • 我非常喜欢这个.几年前我问过这个问题但是使用Java 8这是一个非常好的解决方案. (3认同)