HashSet 基于 HashMap。
如果我们查看 HashSet<E>
实现,所有内容都在 HashMap<E,Object>
下进行管理。
<E>
用作 HashMap
的键。
而且我们知道 HashMap
不是线程安全的。这就是我们在 Java 中使用 ConcurrentHashMap
的原因。
基于此,我很困惑为什么我们没有应该基于 ConcurrentHashMap
的 ConcurrentHashSet?
还有什么我想念的吗?我需要在多线程环境中使用 Set
。
另外,如果我想创建自己的 ConcurrentHashSet
,是否可以通过将 HashMap
替换为 ConcurrentHashMap
并将其余部分保持原样来实现它?
ConcurrentSkipListSet
中看到的内容是基于 ConcurrentSkipListMap
构建的,它实现了 ConcurrentNavigableMap
和 ConcurrentMap
。
ConcurrentHashSet
没有内置类型,因为您始终可以从地图中派生一个集合。由于有许多类型的地图,您可以使用一种方法从给定的地图(或地图类)生成一个集合。
在 Java 8 之前,您可以使用 Collections.newSetFromMap(map)
生成由并发哈希映射支持的并发哈希集
在 Java 8(@Matt 指出)中,您可以通过 ConcurrentHashMap.newKeySet()
获取并发散列集 view。这比旧的 newSetFromMap
要简单一些,旧的 newSetFromMap
需要您传入一个空的地图对象。但它特定于 ConcurrentHashMap
。
无论如何,Java 设计者可以在每次创建新的地图界面时创建一个新的集合界面,但是当第三方创建他们自己的地图时,这种模式将无法实施。最好有派生新集合的静态方法;即使您创建自己的地图实现,这种方法也始终有效。
Set<String> mySet = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
使用 Guava 15 您还可以简单地使用:
Set s = Sets.newConcurrentHashSet();
就像 Ray Toal 提到的那样简单:
Set<String> myConcurrentSet = ConcurrentHashMap.newKeySet();
ConcurrentHashMap
的包装。
看起来 Java 通过其 ConcurrentSkipListSet 提供了并发 Set 实现。 SkipList Set 只是一种特殊的集合实现。它仍然实现了 Serializable、Cloneable、Iterable、Collection、NavigableSet、Set、SortedSet 接口。如果您只需要 Set 界面,这可能对您有用。
ConcurrentSkipListSet
的元素应为 Comparable
SortedSet
,否则不要使用 ConcurrentSkipListSet
。像添加或删除这样的常用操作对于 HashSet
应该是 O(1),而对于 SortedSet
应该是 O(log(n))。
正如 this 所指出的,获得并发 HashSet 的最佳方法是通过 Collections.synchronizedSet()
Set s = Collections.synchronizedSet(new HashSet(...));
这对我有用,我还没有看到有人真正指出它。
EDIT 正如 Eugene 指出的那样,这比当前已批准的解决方案效率低,因为它只是将您的集合包装到同步装饰器中,而 ConcurrentHashMap
实际上实现了低级并发,它可以支持您的设置一样好。感谢 Stepanenkov 先生明确表示。
http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#synchronizedSet-java.util.Set-
synchronizedSet
方法只是在 Collection
下创建 decorator 以包装可以通过同步整个集合来实现线程安全的方法。但是 ConcurrentHashMap
是使用 non-blocking algorithms 和“低级”同步实现的,没有整个集合的任何锁。因此,出于性能原因,来自 Collections.synchronized
... 的包装器在多线程环境中更差。
您可以使用番石榴的 Sets.newSetFromMap(map)
来获得一个。 Java 6 在 java.util.Collections
中也有该方法
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;
}
}
为什么不使用:来自 java.util.concurrent 的 CopyOnWriteArraySet?
CopyOnWriteArraySet.contains()
的运行时间为 O(n)
(必须检查任何条目),而 HashSet/HashMap 有 O(1)
。
ConcurrentHashMap
创建集合,您将失去从ConcurrentHashMap
获得的好处?newSetFromMap
的实现从 docjar.com/html/api/java/util/Collections.java.html 的第 3841 行开始。这只是一个包装......Collections.newSetFromMap
创建一个SetFromMap
。例如,SetFromMap.removeAll
方法委托给从ConcurrentHashMap$CollectionView.removeAll
继承的KeySetView.removeAll
。这种方法在批量去除元素方面效率很低。想象一下removeAll(Collections.emptySet())
遍历Map
中的所有元素而不做任何事情。在大多数情况下,正确实现ConcurrentHashSet
会更好。