【问题标题】:Consume elements from ConcurrentHashMap使用来自 ConcurrentHashMap 的元素
【发布时间】:2016-03-14 10:27:30
【问题描述】:

我使用 ConcurrentHashMap 来缓冲 websocket 的消息。

缓冲区每 200 毫秒以一个 JSON 字符串发送。之后我想从地图中删除该条目。

我猜这不安全,因为在 foreach 期间可能会有新消息进入缓冲区。

String sendString;
for(String msg : buffer.values()){
    sendString += msg;
}
ws.send(sendString);
buffer.clear();

我怎样才能安全地移除元素?

【问题讨论】:

  • 为什么不排队?更适合生产者-消费者场景

标签: java hashmap java.util.concurrent concurrenthashmap


【解决方案1】:

你需要检查key和value。

StringBuilder sendString = new StringBuilder();
for (Map.Entry<String, String> entry : buffer.entrySet()) {
    sendString.append(entry.getValue());
    buffer.remove(entry.getKey(), entry.getValue()); // only remove a matching value
}

【讨论】:

    【解决方案2】:

    Map#remove() 返回移除之前存在于地图中的值。在ConcurrentHashMap 中,这是以原子方式执行的,因此您可以安全地使用结果:

    StringBuilder sendString = new StringBuilder();
    for (String key : buffer.keySet()) {
        String s = buffer.remove(key);
        if (s != null)
            sendString.append(s);
    }  
    

    【讨论】:

      【解决方案3】:

      显式使用迭代器:

      StringBuilder result = new StringBuilder();
      for (Iterator<String> it = buffer.values().iterator(); it.hasNext(); ) {
          result.append(it.next());
          it.remove();
      }
      

      【讨论】:

      • 在it.next() 和it.remove() 之间可能会插入到此键,并且可能会删除刚刚插入的值。
      • 迭代器不会对键进行迭代,而是对值进行迭代。根据 javadocs ConcurrentHashMap 是线程安全的,因此它不会删除与迭代器检索的不同的键值对。
      • BaseIterator#remove(),值迭代器基于它,使用内部map.replaceNode(p.key, null, null),它通过键而不是值删除节点
      • 迭代器线程安全意味着对其进行修改不会破坏地图本身的一致状态,仅此而已。
      • 不幸的是,迭代器remove() 不会返回已删除的值,否则可以以简单安全的方式实现迭代消耗。
      【解决方案4】:

      将整个方法块放在同步块中。执行此代码时,没有其他线程可以写入映射。

      synchronized(buffer){
          String sendString;
          for(String msg : buffer.values()){
              sendString += msg;
          }
          ws.send(sendString);
          buffer.clear();
      }
      
      synchronized(buffer){
         buffer.put(key, msg);
      }
      

      【讨论】:

      • 哦,这将扼杀 CHM 提供的并发环境中的所有好处
      • 是的.. 但是 OP 需要当时的地图快照,而仅使用 CHM 的属性是不可能的
      • @rahulroc “迭代器和枚举返回反映哈希表在创建迭代器/枚举时或之后的某个时间点的状态的元素。” - javadoc 对 ConcurrentHashMap,JDK 7 的评论。所以它很友好快照,但不一定在创建时。
      • 事实并非如此。如果它是一个同步的地图,但 CHM 使用不同的锁定机制。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-11-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-16
      相关资源
      最近更新 更多