为什么没有针对ConcurrentHashMap的ConcurrentHashSet


537

HashSet基于HashMap。

如果我们看一下HashSet<E>实现,则所有内容都在下管理HashMap<E,Object>

<E>用作的键HashMap

而且我们知道这HashMap不是线程安全的。这就是为什么我们使用ConcurrentHashMapJava。

基于此,我感到困惑的是,为什么我们没有应该基于的ConcurrentHashSet ConcurrentHashMap

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

另外,如果我想创建自己的作品,是否ConcurrentHashSet可以通过仅替换HashMapto ConcurrentHashMap并将其余内容保持不变来实现?


2
看完API之后,如果我想说它似乎可以归结为两个因素,(1)避免必须为所需的所有功能创建Java API类(2)更常用的对象。我个人更喜欢LinkedHashMap和LinkedHashSet,因为它们保证顺序与插入顺序相同,所以使用集合的唯一原因是避免重复,通常我还是要保持插入顺序。
阿里

1
@Ali,我个人更喜欢LinkedHashMap和LinkedHashSet你会走得很远:)
bestsss 2011年

9
有点老的问题了,但它是Google的第一个结果,所以知道ConcurrentSkipListSet已经具有ConcurrentHashMap的实现可能会很有用。见docs.oracle.com/javase/7/docs/api/java/util/concurrent/...
伊戈尔·罗德里格斯

1
我从Java源码中看到的ConcurrentSkipListSet是基于ConcurrentSkipListMap,实现ConcurrentNavigableMap和的ConcurrentMap
塔拉·艾哈迈德·汗

Answers:


580

没有内置类型,ConcurrentHashSet因为您总是可以从地图中导出集合。由于地图的类型很多,因此您可以使用一种方法从给定的地图(或地图类)生成一个集合。

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

在Java 8(由@马特指出的),你可以得到一个并发的哈希集合视图通过ConcurrentHashMap.newKeySet()。这比旧版本newSetFromMap要简单一些,旧版本要求您传入一个空的地图对象。但这是特定于ConcurrentHashMap

无论如何,Java设计人员可以在每次创建新的地图接口时创建一个新的set接口,但是当第三方创建自己的地图时,该模式将无法实施。最好有静态方法来派生新的集合。即使您创建自己的地图实现,该方法也始终有效。


4
我说对了,如果您以此方式创建集合ConcurrentHashMap,就会失去从中获得的收益ConcurrentHashMap吗?
Pacerier

19
没有损失的好处。 newSetFromMap可从docjar.com/html/api/java/util/Collections.java.html的第3841行开始找到的实现。这只是一个包装....
雷特里

4
@Andrew,我认为使用“ ConcurrentSet”的动机不是源于API,而是源于实现-线程安全但没有通用锁- 例如多个并发读取。
Ustaman Sangat 2012年

5
ConcurrentSkipList有很多(大小)开销,并且查找速度较慢。
eckes 2015年

3
使用这种方法时请多加注意,因为某些方法未正确实施。只需点击链接:Collections.newSetFromMap创建一个SetFromMap。例如SetFromMap.removeAll方法委托给KeySetView.removeAll继承自的ConcurrentHashMap$CollectionView.removeAll。该方法在大量去除元素方面效率极低。想象removeAll(Collections.emptySet())遍历中的所有元素Map而不做任何事情。有一个ConcurrentHashSet被corretly实施将是在大多数情况下更好。
benez '18

104
Set<String> mySet = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());

79

使用番石榴 15,您还可以简单地使用:

Set s = Sets.newConcurrentHashSet();

12
这始终是一场噩梦。如果您有一个集合或一个映射图,该集合或映射图未指示某项是否是线程安全的,则在维护中会发现各种危险和灾难。我总是想要一个指示集合(或不集合)的线程安全性的类型。
Martin Kersten 2015年

11
方法说明的字面意思是“创建由哈希映射支持的线程安全集”
kichik

16
如我所说,缺少ConcurrentSet <E>。ConcurrentHashMap带有一个ConcurrentMap接口来表明这一点。这也是我总是也添加此ConcurrentSet接口的原因。
马丁·克斯顿


20

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


12
请注意,ConcurrentSkipListSet元素应为Comparable
user454322,2014年

如果需要从并发的Set扩展,则这是唯一可行的解​​决方案。
ndm13,2013年

ConcurrentSkipListMap增加了将树作为基础数据结构而不是使用HashTable的不必要的性能损失,即使您不需要排序/导航功能也是如此。
Ajeet Ganga

ConcurrentSkipListSet除非您想要一个,否则不要使用SortedSet。像add或remove这样的常规操作对于a应该为O(1)HashSet,而对于a为O(log(n))SortedSet
benez '18

16

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

Set s = Collections.synchronizedSet(new HashSet(...));

这对我有用,但我还没有看到有人真正指出它。

编辑 Eugene指出,此方法的效率不如当前经过验证的解决方案,因为它只是将您的集合包装到一个同步的装饰器中,而ConcurrentHashMap实际上实现了低级并发性,并且可以很好地支持您的集合。因此,感谢Stepanenkov先生阐明了这一点。

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


16
synchronizedSet方法只是在下面创建装饰器Collection以包装可以通过同步整个集合进行线程安全的方法。但是ConcurrentHashMap使用非阻塞算法和“低级”同步来实现,而没有整个集合的任何锁定。因此Collections.synchronized出于性能原因,...的包装在多线程环境中更差。
Eugene Stepanenkov 2015年

12

您可以使用番石榴Sets.newSetFromMap(map)来得到一个。Java 6在java.util.Collections


它在java.utll.Collections中可用,而CHM集合通常反而是一件坏事。
bestsss 2011年

是的,我注意到它是Java 6中新增,所以把它添加到答案
Bozho

主要的是,如果它是ThreadSafe,我真的对此表示怀疑。
塔拉·艾哈迈德·汗

@Talha,它是线程安全的,但是仅线程安全就没有任何意义
11

有时,这意味着一切。除非它是算法的一部分,否则通常不会带来性能问题,而算法通常是通过以最小化并发映射的方式实现的。
Martin Kersten 2015年

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

2
我喜欢使用Boolean.TRUE代替虚拟对象的想法。有点优雅。也可以使用NULL,因为即使映射为null,它也可以在键集中使用。
Martin Kersten 2015年

2
@MartinKersten fyi,ConcurrentHashMap不允许使用空值
Lauri Lehtinen

2

为什么不使用:java.util.concurrent中的CopyOnWriteArraySet?


6
因为CopyOnWriteArraySet会在任何状态突变时复制整个集合,由于性能的影响,通常不希望这样做。它仅设计用于特殊情况。
boneash
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.