简体   繁体   English

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

[英]Concurrent frequency counter - concurrency issue

I would like to create a concurrent frequency counter class in Java. 我想在Java中创建一个并发频率计数器类。

It's about that once a request is processed (by processRequest method), the code checks the request's type (an integer) and counts how many requests have been processed (grouped by the request's type) from a given time. 一旦处理了请求(通过processRequest方法),代码就会检查请求的类型(整数),并计算从给定时间开始处理的请求数(按请求的类型分组)。 The processRequest method will be called by multiple threads in the same time. processRequest方法将由多个线程同时调用。

There are two other methods: 还有另外两种方法:

  • clearMap(): It will be called by one thread in every 3 hours and clears the whole map. clearMap():每隔3小时由一个线程调用并清除整个地图。
  • getMap(): It can be called in any time by a webservice and returns an immutable copy of the current state of the frequency map. getMap():它可以在任何时候由Web服务调用,并返回频率映射的当前状态的不可变副本。

See below my initial plan to implement that. 请参阅下面我实施该计划的初步计划。

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);
     }
}

I checked the documentation of ConcurrentHashMap and it tells that the merge method is performed atomically. 我查看了ConcurrentHashMap的文档,它告诉我们以原子方式执行merge方法。

So once the clear() method starts to clear the hash buckets of the map (locking as per hash bucket), it can't be invoked when another thread is between getting the value of the frequency map and incrementing its value in the processRequest method because the merge method is executed atomically. 因此,一旦clear()方法开始清除映射的散列桶(按照散列桶锁定),当另一个线程在获取频率映射的值并在processRequest方法中递增其值之间时,不能调用它因为merge方法是以原子方式执行的。

Am I right? 我对吗? Does my above plan seem to be fine? 我的上述计划似乎没问题吗?

Thank you for your advice. 感谢您的意见。

First, replace Long with AtomicLong . 首先,用AtomicLong替换Long

Second, use computeIfAbsent . 其次,使用computeIfAbsent

 private final Map<Integer, AtomicLong> frequencyMap = new ConcurrentHashMap<>();

 public void processRequest(Request request){
     frequencyMap.computeIfAbsent(request.type, k -> new AtomicLong())
                 .incrementAndGet();
 }

There are a few reasons why I believe this is a better solution: 我认为这是一个更好的解决方案有几个原因:

  1. The code in the question uses boxed objects, ie (v, d) -> v+1 is really (Long v, Long d) -> Long.valueOf(v.longValue() + 1) . 问题中的代码使用盒装对象,即(v, d) -> v+1实际上是(Long v, Long d) -> Long.valueOf(v.longValue() + 1)

    That code generates extra garbage, which can be avoided by using AtomicLong . 该代码会产生额外的垃圾,使用AtomicLong可以避免这种情况。

    The code here only allocates one object per key, and doesn't require any extra allocations to increment the counter, eg it will still only be the one object even if counter goes to the millions. 这里的代码只为每个键分配一个对象,并且不需要任何额外的分配来递增计数器,例如,即使计数器达到数百万,它仍然只是一个对象。

  2. The unboxing, adding 1, boxing operation will likely take slightly longer than the tightly coded incrementAndGet() operation, increasing the likelyhood of a collision, requiring a re-try in the merge method. 取消装箱,添加1,装箱操作可能比紧密编码的incrementAndGet()操作稍长,增加了碰撞的可能性,需要在merge方法中重新尝试。

  3. Code "purity". 代码“纯度”。 Using a method that takes a "value", which is then entirely ignored, seems wrong to me. 使用一个带有“值”的方法,然后完全被忽略,对我来说似乎是错误的。 It is unnecessary code noise. 这是不必要的代码噪音。

These are of course my opinions. 这些当然是我的意见。 You can make your own decision, but I think this code clarifies the purpose, ie to increment a long counter, in a fully thread-safe way. 您可以自己做出决定,但我认为此代码以完全线程安全的方式阐明了目的,即增加一个long计数器。

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

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