我想用 Java 创建一个并发频率计数器类。
一旦处理了请求(通过 processRequest 方法),代码就会检查请求的类型(整数)并计算给定时间内已处理的请求数量(按请求类型分组)。 processRequest方法会被多个线程同时调用。
还有另外两种方法:
- clearMap():每3小时由一个线程调用,清除整个 map 。
- getMap():Web 服务可以随时调用它,并返回频率图当前状态的不可变副本。
请参阅下面我实现该计划的初步计划。
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 方法是原子执行的。
我说得对吗? 我上面的方案看起来还可以吗?
谢谢你的建议。
最佳答案
首先,替换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
计数器,以完全线程安全的方式。
关于java - 并发频率计数器-并发问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55883900/