【问题标题】:Thread safe Map of Queues队列的线程安全映射
【发布时间】:2014-10-21 21:13:21
【问题描述】:

我想实现一个线程安全的队列映射。

我打算从一张空地图开始。如果键不存在,我想用一个新的队列创建一个新的 Map 条目。如果密钥确实存在,我想添加到队列中。我建议的实现如下:

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

public class StackOverFlowExample {

    private final Map<String, ConcurrentLinkedQueue<String>> map = new ConcurrentHashMap<>();

    public void addElementToQueue(String key, String value){
        if (map.containsKey(key)){
            map.get(key).add(value);
        }
        else{
            ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
            queue.add(value);
            map.put(key, queue);
        }           
    }    
}

我担心的是,当多个线程尝试向 Map 添加新值时,第一个线程会将新的 Map 条目与新的 Queue 放在一起,第二个将等待,然后为 key 放置一个新的队列,而不是而不是添加到队列中。我的并发/并发 API 知识充其量是微不足道的。也许并发是就地避免这种情况?建议将不胜感激。

【问题讨论】:

    标签: java multithreading queue concurrenthashmap


    【解决方案1】:

    这种模式可能已经在 SO 上发布过很多次(有效地添加到并发地图中):

    Queue<String> q = map.get(key);
    if(q == null) {
      q = new ConcurrentLinkedQueue<String>();
      Queue<String> curQ = map.putIfAbsent(key, q);
      if(curQ != null) {
        q = curQ;
      }
    }
    q.add(value);
    

    请注意,从 Java 8 开始,这可以替换为 computeIfAbsent()

    【讨论】:

    • if(curQ != null) { q = curQ; } 这种情况永远不会被执行。
    • @Shine - 这里的想法是 map 正在同时被修改,因此给定 key 的值可能在初始 get()putIfAbsent() 调用之间发生了变化跨度>
    【解决方案2】:

    所以你担心线程 A 和线程 B 会执行以下操作:

    thread A: lock ConcurrentHashMap Look for Queue "x" (not found) unlock ConcurrentHashMap create Queue "x" lock ConcurrentHashMap Insert Queue X unlock ConcurrentHashMap Thread B: Lock ConcurrentHashMap (while thread A is in 'create Queue X') look for queue X (not found) unlock ConcurrentHashMap (thread A then gets lock) create Queue "x" v2 lock ConcurrentHashMap Insert Queue X v2 (overwriting the old entry) unlock ConcurrentHashMap

    这实际上是一个真正的问题,但可以通过将 AddElementToQueue 设置为同步方法来轻松解决。那么在任何给定时间,AddElementToQueue 内只能有一个线程,因此第一个“解锁”和第二个“锁定”之间的同步漏洞被关闭。

    因此

    public synchronized void addElementToQueue(String key, String value){

    应该可以解决您丢失队列的问题。

    【讨论】:

    • 如果 ConcurrentHashMap 在检索时使用锁定,那将是有意义的。它没有。
    • 嗯,对,ConcurrentHashMap“仅仅”使用了具有多阅读器弹性的数据结构。锁定仅由编写者在从地图中添加或删除元素所需的短暂时间内使用。我一直忘记那个实现细节,因为从编程的角度来看它真的无关紧要。问题依旧出现在同一个地方(同时“X是否存在于hashmap中”和“创建一个新的hashmap”同时出现),解决方法是一样的——同步addElementToQueue方法。
    • 使用同步块,他并不真正需要并发数据结构,除非代码有更多。
    • 正如这个特定的 StackOverflow 条目所解释的,如果他有多个线程,这是一个不好的建议:stackoverflow.com/questions/1003026/hashmap-concurrency-issue
    • 该链接中的问题专门指的是“无锁定”,这不是我所说的。
    【解决方案3】:

    如果 Java 8 是一个选项:

    public void addElementToQueue(String key, String value) {
        map.merge(key, new ConcurrentLinkedQueue<>(Arrays.asList(value)), (oldValue, coming) -> {
            oldValue.addAll(coming);
            return oldValue;
        });
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-07-08
      • 2011-03-14
      • 2020-08-26
      • 1970-01-01
      • 2012-11-05
      相关资源
      最近更新 更多