为什么ConcurrentHashMap没有ConcurrentHashSet

Tal*_*han 496 java collections concurrency hashmap hashset

HashSet基于HashMap.

如果我们看一下HashSet<E>实现,一切都在管理之下HashMap<E,Object>.

<E>被用作关键词HashMap.

我们知道这HashMap不是线程安全的.这就是我们ConcurrentHashMap在Java中的原因.

基于此,我很困惑,为什么我们没有一个应该基于的ConcurrentHashSet ConcurrentHashMap

还有什么我想念的吗?我需要Set在多线程环境中使用.

另外,如果我想创建我自己的ConcurrentHashSet,我可以通过替换HashMapto来实现它,ConcurrentHashMap并将其余部分保留原样?

Ray*_*oal 547

没有内置类型,ConcurrentHashSet因为您始终可以从地图派生集合.由于有许多类型的地图,因此您可以使用方法从给定地图(或地图类)生成集合.

在Java 8之前,您可以使用生成由并发哈希映射支持的并发哈希集 Collections.newSetFromMap(map)

在Java 8(由@马特指出的),你可以得到一个并发的哈希集合视图通过ConcurrentHashMap.newKeySet().这比旧的newSetFromMap传递一个空的地图对象要简单一点.但它具体到ConcurrentHashMap.

无论如何,每次创建新的地图界面时,Java设计者都可以创建一个新的集合界面,但是当第三方创建自己的地图时,这种模式将无法实施.最好使用派生新集的静态方法; 即使您创建自己的地图实现,该方法也始终有效.

  • 失去任何好处都没有.`newSetFromMap`的实现可以从http://www.docjar.com/html/api/java/util/Collections.java.html的3841行开始.它只是一个包装.... (18认同)
  • ConcurrentSkipList有很多(大小)开销,查找速度较慢. (5认同)
  • 我是否正确地说,如果你从`ConcurrentHashMap`以这种方式创建集合,你会失去从ConcurrentHashMap获得的好处? (4认同)
  • @Andrew,我认为使用"ConcurrentSet"背后的动机不是源于API而是源于实现 - 线程安全但没有通用锁 - 例如多个*并发*读取. (4认同)
  • 使用此方法时要小心,因为某些方法未正确实现.只需按照链接:``Collections.newSetFromMap``创建一个``SetFromMap``.例如``SetFromMap.removeAll``方法委托给``KeySetView.removeAll``,它继承自``ConcurrentHashMap $ CollectionView.removeAll``.该方法在批量移除元素方面效率很低.想象``removeAll(Collections.emptySet())``遍历``Map``中的所有元素而不做任何事情.在大多数情况下,具有正确实现的"ConcurrentHashSet"将更好. (3认同)
  • 我的抱怨是,必须走这条路,你失去了`HashSet(Collection&lt;? extends E&gt; c)` 构造函数,它具有很好的语法糖,例如将 List 转换为 Set。还是我错了? (2认同)
  • 当 JDK 不提供真正的 ConcurrentSet 接口时,真正的问题是您将丢失该 Set 上的一些原子操作,即 `addIfAbsent`、`computeIfAbsent` 等。使用 JDK8,您可以自己创建这些操作,因为 `ConcurrentHashMap.KeySetView`使您可以访问底层 ConcurrentMap。使用 JDK7,您会陷入困境,可能不得不求助于创建自己的包装器。 (2认同)
  • @NickNick 这个问题我们可以扩展到整个 Collection API。`HashSet` 只是 `HashMap` 的包装,`LinkedHashSet` 是 `LinkedHashMap` 的包装,`TreeSet` 只是 `TreeMap` 的包装...... (2认同)

小智 98

Set<String> mySet = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
Run Code Online (Sandbox Code Playgroud)

  • 正如 Ray Toal 在他的回答中指出的那样,2021 年的首选方法肯定是 ConcurrentHashMap.newKeySet() (它在幕后执行类似的操作),因为它更简洁。 (3认同)

kic*_*hik 74

使用Guava 15,您还可以使用:

Set s = Sets.newConcurrentHashSet();
Run Code Online (Sandbox Code Playgroud)

  • 正如我所说,缺少ConcurrentSet <E>.ConcurrentHashMap附带一个ConcurrentMap接口来指示这一点.这也是我总是添加这个ConcurrentSet接口的原因. (12认同)
  • 这总是一场噩梦.如果您有一个集合或地图不能指示某些东西是否是线程安全的,那么您可以在维护中发现所有类型的危险和灾难.我总是想要一个表示集合的线程安全性的类型(或不是). (10认同)
  • 方法描述字面意思是"创建由哈希映射支持的线程安全集" (10认同)

Bul*_*aza 29

就像Ray Toal所提到的那样简单:

Set<String> myConcurrentSet = ConcurrentHashMap.newKeySet();
Run Code Online (Sandbox Code Playgroud)


Mik*_*one 19

看起来Java提供了ConcurrentSkipListSet的并发Set实现.一个SkipList集只是一种特殊的设定实施.它仍然实现Serializable,Cloneable,Iterable,Collection,NavigableSet,Set,SortedSet接口.如果您只需要Set接口,这可能对您有用.

  • 注意`ConcurrentSkipListSet`的元素应该是`Comparable` (12认同)
  • 不要使用 ``ConcurrentSkipListSet`` 除非你想要一个 ``SortedSet``。像添加或删除这样的常规操作对于“HashSet”应该是 O(1),而对于“SortedSet”应该是 O(log(n))。 (3认同)

Nir*_*rro 15

正如指出的这个获得并发能够HashSet的最好的办法就是借助于Collections.synchronizedSet()

Set s = Collections.synchronizedSet(new HashSet(...));
Run Code Online (Sandbox Code Playgroud)

这对我有用,我没有看到任何人真正指向它.

编辑这比当前被批准的解决方案效率低,正如Eugene所指出的那样,因为它只是将你的集合包装成一个同步的装饰器,而ConcurrentHashMap实际上实现了低级别的并发性,它可以将你的集合恢复得很好.所以感谢Stepanenkov先生说清楚了.

http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#synchronizedSet-java.util.Set-

  • `synchronizedSet`方法只是在`Collection`下创建[decorator](http://en.wikipedia.org/wiki/Decorator_pattern),通过同步整个集合来包装可以线程安全的方法.但是`ConcurrentHashMap`是使用[非阻塞算法](https://en.wikipedia.org/wiki/Non-blocking_algorithm)和"低级"同步实现的,没有任何整个集合的锁定.因此,出于性能原因,来自`Collections.synchronized`的包装器......在多线程环境中更糟糕. (15认同)

Boz*_*zho 12

你可以用番石榴Sets.newSetFromMap(map)来买一个.Java 6也有这种方法java.util.Collections


MD.*_*med 5

import java.util.AbstractSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;


public class ConcurrentHashSet<E> extends AbstractSet<E> implements Set<E>{
   private final ConcurrentMap<E, Object> theMap;

   private static final Object dummy = new Object();

   public ConcurrentHashSet(){
      theMap = new ConcurrentHashMap<E, Object>();
   }

   @Override
   public int size() {
      return theMap.size();
   }

   @Override
   public Iterator<E> iterator(){
      return theMap.keySet().iterator();
   }

   @Override
   public boolean isEmpty(){
      return theMap.isEmpty();
   }

   @Override
   public boolean add(final E o){
      return theMap.put(o, ConcurrentHashSet.dummy) == null;
   }

   @Override
   public boolean contains(final Object o){
      return theMap.containsKey(o);
   }

   @Override
   public void clear(){
      theMap.clear();
   }

   @Override
   public boolean remove(final Object o){
      return theMap.remove(o) == ConcurrentHashSet.dummy;
   }

   public boolean addIfAbsent(final E o){
      Object obj = theMap.putIfAbsent(o, ConcurrentHashSet.dummy);
      return obj == null;
   }
}
Run Code Online (Sandbox Code Playgroud)

  • @MartinKersten 仅供参考,ConcurrentHashMap 不允许空值 (3认同)
  • 我喜欢使用Boolean.TRUE而不是虚拟对象的想法.它更优雅一点.也可以使用NULL,因为即使映射到null,它也可以在密钥集中使用. (2认同)