java.util.concurrent.CopyOnWriteArrayList是一个线程安全的 ArrayList,其修改操作是在底层的一个复制的数组(快照)上进行的,即用了写时复制策略。
其定义如下:
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8673264195747942595L; /** * The lock protecting all mutators. (We have a mild preference * for builtin monitors over ReentrantLock when either will do.) */ final transient Object lock = new Object(); /** The array, accessed only via getArray/setArray. */ private transient volatile Object[] array; /** * Gets the array. Non-private so as to also be accessible * from CopyOnWriteArraySet class. */ final Object[] getArray() { return array; }
其构造函数如下:
/** * Creates an empty list. */ public CopyOnWriteArrayList() { setArray(new Object[0]); } /** * Creates a list containing the elements of the specified * collection, in the order they are returned by the collection's * iterator. * * @param c the collection of initially held elements * @throws NullPointerException if the specified collection is null */ public CopyOnWriteArrayList(Collection<? extends E> c) { Object[] es; if (c.getClass() == CopyOnWriteArrayList.class) es = ((CopyOnWriteArrayList<?>)c).getArray(); else { es = c.toArray(); if (c.getClass() != java.util.ArrayList.class) es = Arrays.copyOf(es, es.length, Object[].class); } setArray(es); } /** * Creates a list holding a copy of the given array. * * @param toCopyIn the array (a copy of this array is used as the * internal array) * @throws NullPointerException if the specified array is null */ public CopyOnWriteArrayList(E[] toCopyIn) { setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class)); }
解释:
针对无参构造函数,其内部创建了一个大小为 0 的 Object 数组作为array 的初始值。
其添加元素的方法有多个,如下:
add(E e)的定义如下:
/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return {@code true} (as specified by {@link Collection#add}) */ public boolean add(E e) { synchronized (lock) { Object[] es = getArray(); int len = es.length; es = Arrays.copyOf(es, len + 1); es[len] = e; setArray(es); return true; } }
CopyOnWriteArrayList中获取指定位置的元素的方法如下:
/** * {@inheritDoc} * * @throws IndexOutOfBoundsException {@inheritDoc} */ public E get(int index) { return elementAt(getArray(), index); }
static <E> E elementAt(Object[] a, int index) { return (E) a[index]; }
解说:
从上述代码可以知道,当线程 x调用 get方法获取指定位置的元素时需要两步:
这个两步操作在整个过程中并没有加锁。
问题:假设线程 x执行完步骤 A 后,在执行步骤 B 前,线程 y进行了 remove操作,这时会发生什么?
remove方法的代码如下:
/** * Removes the element at the specified position in this list. * Shifts any subsequent elements to the left (subtracts one from their * indices). Returns the element that was removed from the list. * * @throws IndexOutOfBoundsException {@inheritDoc} */ public E remove(int index) { synchronized (lock) { Object[] es = getArray(); int len = es.length; E oldValue = elementAt(es, index); int numMoved = len - index - 1; Object[] newElements; if (numMoved == 0) newElements = Arrays.copyOf(es, len - 1); else { newElements = new Object[len - 1]; System.arraycopy(es, 0, newElements, 0, index); System.arraycopy(es, index + 1, newElements, index, numMoved); } setArray(newElements); return oldValue; } }
remove操作获取独占锁,然后进行写时复制操作,在复制的数组里删除 index 处的元素,随后让 array 指向新复制的数组。
上图指出了线程 x 执行步骤B时操作的数组是线程 y 删除元素之前线程 x 复制的数组。
所以,线程 y 虽然己经删除了 index 处的元素,但是线程 x 的步骤 B 还是会返回index 处的元素,这就是写时复制策略的弱一致性问题。
大家都知道可以使用法代器遍历List的元素,当调用 CopyOnWriteArrayList 的 iterator()方法获取法代器时,实际上会返回一个 COWiterator对象。
/** * Returns an iterator over the elements in this list in proper sequence. * * <p>The returned iterator provides a snapshot of the state of the list * when the iterator was constructed. No synchronization is needed while * traversing the iterator. The iterator does <em>NOT</em> support the * {@code remove} method. * * @return an iterator over the elements in this list in proper sequence */ public Iterator<E> iterator() { return new COWIterator<E>(getArray(), 0); }
static final class COWIterator<E> implements ListIterator<E> { /** Snapshot of the array */ private final Object[] snapshot; /** Index of element to be returned by subsequent call to next. */ private int cursor; COWIterator(Object[] es, int initialCursor) { cursor = initialCursor; snapshot = es; } public boolean hasNext() { return cursor < snapshot.length; } public boolean hasPrevious() { return cursor > 0; } @SuppressWarnings("unchecked") public E next() { if (! hasNext()) throw new NoSuchElementException(); return (E) snapshot[cursor++]; } @SuppressWarnings("unchecked") public E previous() { if (! hasPrevious()) throw new NoSuchElementException(); return (E) snapshot[--cursor]; } public int nextIndex() { return cursor; } public int previousIndex() { return cursor - 1; } /** * Not supported. Always throws UnsupportedOperationException. * @throws UnsupportedOperationException always; {@code remove} * is not supported by this iterator. */ public void remove() { throw new UnsupportedOperationException(); } /** * Not supported. Always throws UnsupportedOperationException. * @throws UnsupportedOperationException always; {@code set} * is not supported by this iterator. */ public void set(E e) { throw new UnsupportedOperationException(); } /** * Not supported. Always throws UnsupportedOperationException. * @throws UnsupportedOperationException always; {@code add} * is not supported by this iterator. */ public void add(E e) { throw new UnsupportedOperationException(); } @Override public void forEachRemaining(Consumer<? super E> action) { Objects.requireNonNull(action); final int size = snapshot.length; int i = cursor; cursor = size; for (; i < size; i++) action.accept(elementAt(snapshot, i)); } }
解说:
COWiterator对象的 snapshot变量保存了当前 array的内容,cursor是遍历数据的下标。
在线程使用返回的迭代器遍历元素的过程中,如果有其他线程对 list 进行了增删改,那么 snapshot就是快照了。
因为增删改后指向 array的变量(即本文最开始着色了的那行代码,private transient volatile Object[] array;)指向了新数组。
所以在获取迭代器遍历元素时,其他线程对 list 进行的增删改对外不可见,因为它们操作的是两个不同的数组,这就是弱一致性。
java.util.concurrent.CopyOnWriteArraySet是在CopyOnWriteArrayList的基础上使用了装饰模式,其很多方法都是调用CopyOnWriteArrayList的API来实现的。
其添加元素的代码如下:
/** * Adds the specified element to this set if it is not already present. * More formally, adds the specified element {@code e} to this set if * the set contains no element {@code e2} such that * {@code Objects.equals(e, e2)}. * If this set already contains the element, the call leaves the set * unchanged and returns {@code false}. * * @param e element to be added to this set * @return {@code true} if this set did not already contain the specified * element */ public boolean add(E e) { return al.addIfAbsent(e); }
解说:
从CopyOnWriteArraySet的定义以及构造函数可以看到其底层数据结构就是CopyOnWriteArrayList,代码如下:
public class CopyOnWriteArraySet<E> extends AbstractSet<E> implements java.io.Serializable { private static final long serialVersionUID = 5457747651344034263L; 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) { if (c.getClass() == CopyOnWriteArraySet.class) { @SuppressWarnings("unchecked") CopyOnWriteArraySet<E> cc = (CopyOnWriteArraySet<E>)c; al = new CopyOnWriteArrayList<E>(cc.al); } else { al = new CopyOnWriteArrayList<E>(); al.addAllAbsent(c); } }