所以,我在另一个地图中有一个地图,例如,它可能是每个帐户基础的字计数器:
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()之间的竞争条件的必要原因,这是正确的吗?
更新
我假设可能会删除子地图和加法器,因为可能有其他方法访问该地图。
答案 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;
});
}