在这篇文章中来说下Java并发容器中的CopyOnWriteArraySet。
CopyOnWriteArraySet是基于CopyOnWriteArrayList容器来实现相关功能的,如下所示:
private final CopyOnWriteArrayList<E> al; /** * Creates an empty set. */ public CopyOnWriteArraySet() { al = new CopyOnWriteArrayList<E>(); } /** * Creates a set containing all of the elements of the specified * collection. * * @param c the collection of elements to initially contain * @throws NullPointerException if the specified collection is null */ public CopyOnWriteArraySet(Collection<? extends E> c) { al = new CopyOnWriteArrayList<E>(); al.addAllAbsent(c); }
该容器中提供的所有对元素的各种操作方法底层实际都是对CopyOnWriteArrayList容器的操作,关于CopyOnWriteArrayList的容器在另一篇文章《Java并发容器之CopyOnWriteArrayList》中进行了简单的说明。
下面看下这个容器是如何进行新增删除和查看元素的。
/** * 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 * the 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 al.addIfAbsent(e); }
/** * Adds all of the elements in the specified collection to this set if * they're not already present. If the specified collection is also a * set, the <tt>addAll</tt> operation effectively modifies this set so * that its value is the <i>union</i> of the two sets. The behavior of * this operation is undefined if the specified collection is modified * while the operation is in progress. * * @param c collection containing elements to be added to this set * @return <tt>true</tt> if this set changed as a result of the call * @throws NullPointerException if the specified collection is null * @see #add(Object) */ public boolean addAll(Collection<? extends E> c) { return al.addAllAbsent(c) > 0; }
从上面可以看出,CopyOnWriteArraySet添加元素是通过调用CopyOnWriteArrayList的addIfAbsent(e)和addAllAbsent(c)方法来实现添加元素的。这些方法也就是在添加元素时判断所添加元素是否已在容器中的数组中已存在。回到CopyOnWriteArrayList的源码上,如下所示:
/** * Append the element if not present. * * @param e element to be added to this list, if absent * @return <tt>true</tt> if the element was added */ public boolean addIfAbsent(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { // Copy while checking if already present. // This wins in the most common case where it is not present Object[] elements = getArray(); int len = elements.length; Object[] newElements = new Object[len + 1]; for (int i = 0; i < len; ++i) { if (eq(e, elements[i])) return false; // exit, throwing away copy else newElements[i] = elements[i]; } newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); } }
在添加元素时创建了一个新数组,然后遍历每个元素看看待添加对象是否和数组中已有对象相等,如果是,则直接返回失败,否则将老数组中的元素赋值到新数组中并添加新的元素并返回成功。由于整个操作涉及数组的元素的变化,因此整个过程是在锁的保护下完成的。
而对于集合元素的添加,先判断是否集合中有新元素,如果有,则通过创建新数组,然后把原数组元素和新集合中不存在的元素都System.arraycopy方法将元素拷贝到新数组中。如下所示:
/** * Appends all of the elements in the specified collection that * are not already contained in this list, to the end of * this list, in the order that they are returned by the * specified collection's iterator. * * @param c collection containing elements to be added to this list * @return the number of elements added * @throws NullPointerException if the specified collection is null * @see #addIfAbsent(Object) */ public int addAllAbsent(Collection<? extends E> c) { Object[] cs = c.toArray(); if (cs.length == 0) return 0; Object[] uniq = new Object[cs.length]; final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; int added = 0; for (int i = 0; i < cs.length; ++i) { // scan for duplicates Object e = cs[i]; if (indexOf(e, elements, 0, len) < 0 && indexOf(e, uniq, 0, added) < 0) uniq[added++] = e; } if (added > 0) { Object[] newElements = Arrays.copyOf(elements, len + added); System.arraycopy(uniq, 0, newElements, len, added); setArray(newElements); } return added; } finally { lock.unlock(); } }
而对于元素的获取,其实和CopyOnWriteArrayList一样,并不存在并发问题,只要根据数组索引来获取元素返回即可。
总体来说,这个容器并没有太多逻辑,基于它的内部实现,其实和CopyOnWriteArrayList存在一样的并发写性能问题,因此也是适合读多写少的场合。
感谢大家的阅读,如果有对Java编程、中间件、数据库、及各种开源框架感兴趣,欢迎关注我的博客和头条号(源码帝国),博客和头条号后期将定期提供一些相关技术文章供大家一起讨论学习,谢谢。
如果觉得文章对您有帮助,欢迎给我打赏,一毛不嫌少,一百不嫌多,^_^谢谢。