从文章标题看,你可能很疑惑JDK中没有ConcurrentHashSet这个类啊,说对了,这篇文章就是分享怎样使用 ConcurrentHashMap 来试下 ConcurrentHashSet。
实现ConcurrentHashSet之前,我们先来回顾一下Set接口有哪些子类?我们先看下类图:
从上面看,set的子类有四个,其中 :HaseSet、TreeSet、LinkedHashSet.这三个都是 线程 不安全的,CopyOnWriteArraySet是 线程安全 的。
即使CopyOnWriteArraySet 是线程安全的,它也不适合大型线程安全集合的应用程序,它仅用于设置大小保持较小且读大于写的应用程序,那么为什么呢?我们从源码上进行分析
CopyOnWriteArraySet 内部是使用 CopyOnWriteArrayList 来实现的,那么我们分析 CopyOnWriteArrayList的添加元素方法。
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public Boolean add(E e) {
final Reentrant lock lock = this.lock;
lock.lock();
try {
Object [] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
上面每次添加元素的时候都需要创建一个新容器,然后把旧容器的元素copy进去,最终把新容器的赋值给旧容器,整个的操作是在锁当中调用的,所以是线程安全的。
那么为什么不建议在并发高的应用程序中使用呢?有以下两点
(1) 性能问题:
每次修改都创建一个新数组,然后复制所有内容,如果数组比较大,修改操作又比较频繁,可以想象,性能是很低的,而且内存开销会很大。
(2) 数据一致性问题。
CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,不要使用CopyOnWrite容器。
ConcurrentHashSet 的创建
可能有的同学会将,我也可以直接使用 ConcurrentHashMap 来进行操作,没必要使用ConcurrentHashSet。事实上,这正是 ConcurrentHashSet的 实现原理,底层是用 ConcurrentHashMap来实现的。
实现起来还是很简单的,因为HashSet 的底层也是使用HashMap来实现的。
public class ConcurrentHashSet<E> extends AbstractSet<E> implements Set<E>, java.io.Serializable {
private static final long serialVersionUID = -8672117787651310382L;
private static final Object PRESENT = new Object();
private final ConcurrentHashMap<E, Object> map;
public ConcurrentHashSet() {
map = new ConcurrentHashMap<E, Object>();
}
public ConcurrentHashSet(int initialCapacity) {
map = new ConcurrentHashMap<E, Object>(initialCapacity);
}
/**
* Returns an iterator over the elements in this set. The elements are
* returned in no particular order.
*
* @return an Iterator over the elements in this set
* @see ConcurrentModificationException
*/
public Iterator<E> iterator() {
return map.keySet().iterator();
}
/**
* Returns the number of elements in this set (its cardinality).
*
* @return the number of elements in this set (its cardinality)
*/
public int size() {
return map.size();
}
/**
* Returns <tt>true</tt> if this set contains no elements.
*
* @return <tt>true</tt> if this set contains no elements
*/
public boolean isEmpty() {
return map.isEmpty();
}
/**
* Returns <tt>true</tt> if this set contains the specified element. More
* formally, returns <tt>true</tt> if and only if this set contains an
* element <tt>e</tt> such that
* <tt>(o==null ? e==null : o.equals(e))</tt>.
*
* @param o element whose presence in this set is to be tested
* @return <tt>true</tt> if this set contains the specified element
*/
public boolean contains(Object o) {
return map.containsKey(o);
}
/**
* Adds the specified element to this set if it is not already present. More
* formally, adds the specified element <tt>e</tt> to this set if this set
* contains no element <tt>e2</tt> such that
* <tt>(e==null ? e2==null : e.equals(e2))</tt>. If this
* set already contains the element, the call leaves the set unchanged and
* returns <tt>false</tt>.
*
* @param e element to be added to this set
* @return <tt>true</tt> if this set did not already contain the specified
* element
*/
public boolean add(E e) {
return map.put(e, PRESENT) == null;
}
/**
* Removes the specified element from this set if it is present. More
* formally, removes an element <tt>e</tt> such that
* <tt>(o==null ? e==null : o.equals(e))</tt>, if this
* set contains such an element. Returns <tt>true</tt> if this set contained
* the element (or equivalently, if this set changed as a result of the
* call). (This set will not contain the element once the call returns.)
*
* @param o object to be removed from this set, if present
* @return <tt>true</tt> if the set contained the specified element
*/
public boolean remove(Object o) {
return map.remove(o) == PRESENT;
}
/**
* Removes all of the elements from this set. The set will be empty after
* this call returns.
*/
public void clear() {
map.clear();
}
}
ConcurrentHashSet继承了AbstractSet,实现了Set、Serializable接口;它底层使用ConcurrentHashMap来实现,其value固定为PRESENT。
其他线程安全的Set 有哪些?
1、java1.6已经帮我们实现了
Set<String> acceptedClassLoaders = Collections.newSetFromMap(new ConcurrentHashMap(16));
2、Collections 的synchronizedSet方法
static <T> Set<T> synchronizedSet(Set<T> s, Object mutex) {
return new SynchronizedSet<>(s, mutex);
}
3、使用 CopyOnWriteArraySet
4、谷歌的guava其实已经实现了线程安全的ConcurrentHashSet
Set<String> s = Sets.newConcurrentHashSet();
public static <E> Set<E> newConcurrentHashSet() {
return newSetFromMap(new ConcurrentHashMap<E, Boolean>());
}
static <E> Set<E> newSetFromMap(Map<E, Boolean> map) {
return Collections.newSetFromMap(map);
}