在另一个映射内的映射中为特定键递增值的线程安全方法

时间:2017-08-30 11:59:45

标签: java multithreading concurrency thread-safety

所以,我在另一个地图中有一个地图,例如,它可能是每个帐户基础的字计数器:

Map<Long, Map<String, Long>>

增加计数器的正确的线程安全方法是什么?

我想可以使用ConcurrentHashMap和LongAdder,如下所示:

private Map<Long, Map<GovernorLimitName, LongAdder>> status = new ConcurrentHashMap<> ();

public void count (Long accountId, String word) {
  status.putIfAbsent (accountId, new ConcurrentHashMap<GovernorLimitName, LongAdder> ());
   synchronized (getStatus ().get (accountId)) {
     getStatus ().get (accountId).computeIfAbsent(limitName, k -> new LongAdder()).increment();
   }
 }

我认为这里的同步化是获取内部地图和对其执行comuteIfAbsent()之间的竞争条件的必要原因,这是正确的吗?

更新

我假设可能会删除子地图和加法器,因为可能有其他方法访问该地图。

3 个答案:

答案 0 :(得分:0)

如果您需要进行同步,那么使用ConcurrentHashMap是没有意义的,因为您获得了值Map<GovernorLimitName, LongAdder>(< em> 同时完成)然后获取LongAdder并递增它()。

而不是使用Long使用AtomicLong并更改实现以使用常规HashMap。

答案 1 :(得分:0)

您不需要synchronized,只要永远不会从status移除子地图,并且永远不会从子地图中删除加法器。

但是,创建一个通常会丢弃的新ConcurrentHashMap实在太贵了。使用您已有的数据结构,您可以这样做:

public void count (Long accountId, GovernorLimitName limitName) {

  Map<GovernorLimitName, LongAdder> submap = status.computeIfAbsent(accountId,
     a -> new ConcurrentHashMap<GovernorLimitName, LongAdder> ());

  LongAdder adder = submap.computeIfAbsent(limitName, k -> new LongAdder());

  adder.incremeent();
}

答案 2 :(得分:0)

而不是在主地图上使用 putIfAbsent ,只需使用 compute 函数(并在该函数内部执行所有操作,甚至是内部地图内容)。如果在根映射中使用ConcurrentHashMap,那么无论你在函数内部做什么都将是线程安全的;如果你这样做,就不需要同步块。

显然,你仍然需要使用这种方法的并发hashmap(两者都有),因为我猜你会在你的代码的某个其他位置进行获取,否则你会在读取数据时遇到并发问题。

可以采用其他方法而不是使用ConcurrentHashMap,但这超出了问题的范围,并且使用这些实现很好。

这里有一些代码(可能有拼写错误,代码风格可以改进):

private Map<Long, Map<GovernorLimitName, LongAdder>> status = new ConcurrentHashMap<> ();

public void count (Long accountId, String word) {
    status.compute(accountId, (k, v) -> {
        if (v == null) {
            v = new ConcurrentHashMap<>();
        }
        v.compute(limitName, (k2, v2) -> {
            if (v2 == null) {
                v2 = new LongAdder();
            }
            v2.increment();
            return v2;
        });
        return v;
    });
 }