繁体   English   中英

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

[英]Concurrent frequency counter - concurrency issue

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

一旦处理了请求(通过processRequest方法),代码就会检查请求的类型(整数),并计算从给定时间开始处理的请求数(按请求的类型分组)。 processRequest方法将由多个线程同时调用。

还有另外两种方法:

  • clearMap():每隔3小时由一个线程调用并清除整个地图。
  • 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方法是以原子方式执行的。

我对吗? 我的上述计划似乎没问题吗?

感谢您的意见。

首先,用AtomicLong替换Long

其次,使用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计数器。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM