并发频率计数器-并发问题

时间:2019-04-27 19:00:59

标签: java concurrency

我想用Java创建一个并发的频率计数器类。

大约是在处理完请求之后(通过processRequest方法),该代码检查请求的类型(整数),并计算给定时间内已处理了多少个请求(按请求的类型分组)。多个线程将同时调用processRequest方法。

还有其他两种方法:

  • clearMap():每3小时将由一个线程调用一次,并清除整个地图。
  • getMap():可以随时通过网络服务调用,并返回频率图当前状态的不变副本。

请参阅下面的实施该计划的初始计划。

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方法是原子执行的。

我是对的吗? 我的上述计划看起来还不错吗?

谢谢您的建议。

1 个答案:

答案 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();
 }

我认为这是一个更好的解决方案有几个原因:

  1. 问题中的代码使用装箱的对象,即(v, d) -> v+1实际上是(Long v, Long d) -> Long.valueOf(v.longValue() + 1)

    该代码会产生额外的垃圾,可以通过使用AtomicLong来避免。

    这里的代码每个键只分配一个对象,不需要任何额外的分配来增加计数器,例如即使计数器达到数百万,它也仍然只是一个对象。

  2. 取消装箱,加1,装箱操作将比严格编码的incrementAndGet()操作花费更长的时间,增加了碰撞的可能性,需要在merge方法中重试

  3. 代码“纯度”。对我来说,使用一种采用“值”的方法,然后将其完全忽略,这是错误的。这是不必要的代码噪音。

这些当然是我的看法。您可以自行决定,但是我认为这段代码阐明了目的,即以完全线程安全的方式增加long计数器。