从文章标题看,你可能很疑惑JDK中没有ConcurrentHashSet这个类啊,说对了,这篇文章就是分享怎样使用 ConcurrentHashMap 来试下 ConcurrentHashSet。
实现ConcurrentHashSet之前,我们先来回顾一下Set接口有哪些子类?我们先看下类图:
![d04444a9817d2318d7f211c5c6deba40.png](https://img-blog.csdnimg.cn/img_convert/d04444a9817d2318d7f211c5c6deba40.png)
从上面看,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 true (as specified by {@link Collection#add}) */ public boolean add(E e) { final ReentrantLock 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 extends AbstractSet implements Set, java.io.Serializable { private static final long serialVersionUID = -8672117787651310382L; private static final Object PRESENT = new Object(); private final ConcurrentHashMap map; public ConcurrentHashSet() { map = new ConcurrentHashMap(); } public ConcurrentHashSet(int initialCapacity) { map = new ConcurrentHashMap(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 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 true if this set contains no elements. * * @return true if this set contains no elements */ public boolean isEmpty() { return map.isEmpty(); } /** * Returns true if this set contains the specified element. More * formally, returns true if and only if this set contains an * element e such that * (o==null ? e==null : o.equals(e)). * * @param o element whose presence in this set is to be tested * @return true 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 e to this set if this set * contains no element e2 such that * (e==null ? e2==null : e.equals(e2)). If this * set already contains the element, the call leaves the set unchanged and * returns false. * * @param e element to be added to this set * @return true 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 e such that * (o==null ? e==null : o.equals(e)), if this * set contains such an element. Returns true 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 true 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 acceptedClassLoaders = Collections.newSetFromMap(new ConcurrentHashMap(16));
2、Collections 的synchronizedSet方法
static Set synchronizedSet(Set s, Object mutex) { return new SynchronizedSet<>(s, mutex);}
3、使用 CopyOnWriteArraySet
![133588e82fdd75f65c4c0426f10d569c.png](https://img-blog.csdnimg.cn/img_convert/133588e82fdd75f65c4c0426f10d569c.png)
4、谷歌的guava其实已经实现了线程安全的ConcurrentHashSet
Set s = Sets.newConcurrentHashSet();public static Set newConcurrentHashSet() { return newSetFromMap(new ConcurrentHashMap());}static Set newSetFromMap(Map map) { return Collections.newSetFromMap(map);}