java中是不是有并发多值HashMap数据结构?

Posted

技术标签:

【中文标题】java中是不是有并发多值HashMap数据结构?【英文标题】:Is there any Concurrent multi valued HashMap data structure in java?java中是否有并发多值HashMap数据结构? 【发布时间】:2018-04-08 15:27:54 【问题描述】:

我需要有键值对,其中的值可以是一个集合。此数据结构应该是线程安全的,以便在多线程环境中将删除元素添加到集合中。 我的要求是创建一个订阅列表,在这里人们可以订阅不同的主题。这个订阅列表应该是并发的、线程安全的和快速的。我在考虑使用 ConcurentHashMap 和 ConcurrentHashSet,这对我没有帮助,因为我必须将同步块放在顶层,它会阻塞整个地图,直到 put/remove 操作完成。

【问题讨论】:

不在 JRE 中。也许在 Apache Commons 等中。 我检查了 Apache 评论,但找不到。有多值哈希映射,但它们不是线程安全的。 可以使用ConcurrentHashMap,值为SET。有什么观察吗? 不能用简单同步吗?它必须是并发结构吗? @shmosel 如果我在顶层使用同步它会阻塞整个地图,那么就没有并发了。 【参考方案1】:

没有预先推出的解决方案,但可以使用ConcurrentMap<K, Set<V>> 实现简单值的线程安全并发,其中Set<V> 值由ConcurrentMap<V, Boolean> 使用Collections.newSetFromMap(Map<V,Boolean>) 生成。

然后,要以原子方式设置每个值,请使用ConcurrentMap.computeIfAbsent(K, Function<? super K, ? extends Set<V>>)

ConcurrentMap<String, Set<Integer>> multimap = new ConcurrentHashMap<>();
Set<Integer> fooValues = multimap.computeIfAbsent("foo", key -> Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>()));

如果你希望你的值有一个稳定的迭代顺序,你可以使用 ConcurrentSkipListSet 来保存值:

ConcurrentMap<String, NavigableSet<Integer>> multimap = new ConcurrentHashMap<>();
NavigableSet<Integer> fooValues = multimap.computeIfAbsent("foo", key -> new ConcurrentSkipListSet<>());

同样,为了以线程安全的方式删除 Set&lt;V&gt; 值持有者实例,您可以使用 ConcurrentMap.computeIfPresent(K, BiFunction&lt;? super K,? super Set&lt;V&gt;,? extends Set&lt;V&gt;&gt;)

public static <K, V> void remove(final ConcurrentMap<K, Collection<? extends V>> multimap, final K key,
        final V value) 
    multimap.computeIfPresent(key, (k, oldValues) -> 
        final Collection<? extends V> newValues;
        if (oldValues.remove(value) && oldValues.isEmpty()) 
            // Remove the empty set from the multimap
            newValues = null;
         else 
            newValues = oldValues;
        
        return newValues;
    );

注意there is no "ConcurrentHashSet" class provided by the Java core libraries。

【讨论】:

抱歉延迟回复。我认为这是最好的方法。让我试试看。【参考方案2】:

你写了“值可以是一个集合”,但没有提到键是什么。无论如何,Java 中出现的第一个映射 Hashtable 是线程安全的,可以添加/删除/替换键值对。

您可以使用here 中描述的方法之一创建线程安全集。该集合是否作为值存储在哈希图中没有区别。

【讨论】:

我对这个解决方案有 2 个问题。第一个 Hashtable 中的线程安全是通过同步添加删除操作来实现的。它使整个 Hashtable 在操作完成之前无法访问。相反,我可以使用 concurrentHashMap。 第二个问题是,我不能同时使用 ConcurrentHashMap 和 ConcurrentHashSet。假设我要为键添加一个值首先我必须检查是否有一个集合已经添加到键中。否则我必须先添加 Set 然后我必须添加值。在这种情况下,另一个线程可能会覆盖该集合。从集合中删除值时也会发生这种情况。移除时 如果移除元素是集合中的最后一个元素 那么我们必须从 HashMap 中移除集合。如果一个线程正在向集合添加值,而另一个线程正在删除集合,会发生什么? 我建议添加您自己的同步包装器,该包装器隐藏设置对象并仅获取设置值。类似于:class MyMap private ConcurrentHashMap> hm = ... void put(K k, V v) synchronized(hm) ConcurrentHashSet hs = hm.get (k) if (hs == null) hs = new ... hs.add(v) ... @Roberto Attias 虽然 put 完成,但没有人可以访问地图,也没有必要使用 ConcurrentHashMap。 对不起:Hashtable 太老了,使用了糟糕的并发策略(即禁止并发);永远不要在非遗留代码中使用它。

以上是关于java中是不是有并发多值HashMap数据结构?的主要内容,如果未能解决你的问题,请参考以下文章

Java并发容器--ConcurrentHashMap

java并发造成HashMap非线程安全的原因

为什么 HashMap 并发时会引起死循环?

线程同步和并发

java并发之hashmap源码

为并发而生的 ConcurrentHashMap(Java 8)