我想用Java创建一个并发的频率计数器类。
大约是在处理完请求之后(通过processRequest方法),该代码检查请求的类型(整数),并计算给定时间内已处理了多少个请求(按请求的类型分组)。多个线程将同时调用processRequest方法。
还有其他两种方法:
请参阅下面的实施该计划的初始计划。
public class FrequencyCounter {
private final ConcurrentHashMap<Integer,Long> frequencenyMap = new ConcurrentHashMap<>();
public void processRequest(Request request){
frequencenyMap.merge(request.type, 0L, (v, d) -> v+1);
}
public void clearMap(){
frequencenyMap.clear();
}
public Map<Integer,Long> getMap(){
return ImmutableMap.copyOf(frequencenyMap);
}
}
我检查了ConcurrentHashMap的文档,并发现merge方法是原子执行的。
因此,一旦clear()方法开始清除映射的哈希桶(按哈希桶锁定),则在获取频率映射的值与递增频率映射的值之间存在另一个线程时,将无法调用该方法。之所以使用processRequest方法,是因为merge方法是原子执行的。
我是对的吗? 我的上述计划看起来还不错吗?
谢谢您的建议。
答案 0 :(得分:0)
首先,将Long
替换为AtomicLong
。
第二,使用computeIfAbsent
。
private final Map<Integer, AtomicLong> frequencyMap = new ConcurrentHashMap<>();
public void processRequest(Request request){
frequencyMap.computeIfAbsent(request.type, k -> new AtomicLong())
.incrementAndGet();
}
我认为这是一个更好的解决方案有几个原因:
问题中的代码使用装箱的对象,即(v, d) -> v+1
实际上是(Long v, Long d) -> Long.valueOf(v.longValue() + 1)
。
该代码会产生额外的垃圾,可以通过使用AtomicLong
来避免。
这里的代码每个键只分配一个对象,不需要任何额外的分配来增加计数器,例如即使计数器达到数百万,它也仍然只是一个对象。
取消装箱,加1,装箱操作将比严格编码的incrementAndGet()
操作花费更长的时间,增加了碰撞的可能性,需要在merge
方法中重试
代码“纯度”。对我来说,使用一种采用“值”的方法,然后将其完全忽略,这是错误的。这是不必要的代码噪音。
这些当然是我的看法。您可以自行决定,但是我认为这段代码阐明了目的,即以完全线程安全的方式增加long
计数器。