java數據結構之CopyOnWriteArrayList和CopyOnWriteArraySet
一、什麽是CopyOnWrite
CopyOnWrite(寫時復制)簡稱COW,這是一種利用讀寫分離的思想來實現線程安全的程序設計思路。顧名思義該思想就是在寫的時候將原數據復制一份,然後在新的數據中進行一些寫入操作。但是讀相關的操作還是在原有的數據上進行,等到寫操作完成之後,用新的數據替換老的數據來達到修改數據的目的。CopyOnWrite只需要在寫操作上加鎖,在讀取的時候也可以進行寫操作,所以效率會更好一點。由於在寫入操作的時候需要復制一份新的數據,更加占內存,所以適合在寫入操作比較少讀取操作比較多的情況下來進行操作。
二、CopyOnWriteArrayList和CopyOnWriteArraySet
java利用了CopyOnWrite這個思想實現了兩個線程安全的集合CopyOnWriteArrayList和CopyOnWriteArraySet,其中CopyOnWriteArraySet的底層還是通過CopyOnWriteArrayList來實現的,所以我們重點來看看CopyOnWriteArrayList。
/** * CopyOnWriteArrayList 是ArrayList的一個線程安全的變體 * 通過在進行add或set等操作的時候復制一份原來的數據 */ public class CopyOnWriteArrayList<E> implementsList<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8673264195747942595L; /** The lock protecting all mutators */ final transient ReentrantLock lock = new ReentrantLock(); /** 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; } /** * Sets the array. */ final void setArray(Object[] a) { array = a; } /** * Creates an empty list. */ public CopyOnWriteArrayList() { setArray(new Object[0]); } /** * 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)); } // Positional Access Operations @SuppressWarnings("unchecked") private E get(Object[] a, int index) { return (E) a[index]; } /** * {@inheritDoc} * * @throws IndexOutOfBoundsException {@inheritDoc} */ public E get(int index) { return get(getArray(), index); } /** * Replaces the element at the specified position in this list with the * specified element. * * @throws IndexOutOfBoundsException {@inheritDoc} */ public E set(int index, E element) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); E oldValue = get(elements, index); if (oldValue != element) { int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len); newElements[index] = element; setArray(newElements); } else { // Not quite a no-op; ensures volatile write semantics setArray(elements); } return oldValue; } finally { lock.unlock(); } } /** * 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) { 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(); } } /** * Removes all of the elements from this list. * The list will be empty after this call returns. */ public void clear() { final ReentrantLock lock = this.lock; lock.lock(); try { setArray(new Object[0]); } finally { lock.unlock(); } } }
通過上面的一些主要方法可以看到在CopyOnWriteArrayList通過數組來保存數據,其內部創建了一個可重入鎖ReentrantLock的實例lock用來對寫相關的操作進行加鎖,來保證同一時間只有一個副本在進行寫操作。CopyOnWriteArrayList不太適合寫操作比較多的環境,因為頻繁的寫操作會產生很多垃圾,是的垃圾回收壓力會比較大。
三、CopyOnWriteArrayList的叠代器
CopyOnWriteArrayList中的叠代器為COWIterator,從代碼中可以看出,該叠代器本身是基於當前的數組來創建的,並且叠代器中的方法add、set和remove是不讓使用的,這樣就避免了和寫方法沖突。
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; private COWIterator(Object[] elements, int initialCursor) { cursor = initialCursor; snapshot = elements; } 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); Object[] elements = snapshot; final int size = elements.length; for (int i = cursor; i < size; i++) { @SuppressWarnings("unchecked") E e = (E) elements[i]; action.accept(e); } cursor = size; } }
四、CopyOnWriteArrayList和ArrayList以及Vector
Vector:在這三者中出現的最早,並且是線程安全的,它的讀寫方法上都使用了synchronized關鍵字用內置鎖對方法進行了加鎖,所以讀寫操作都需要對鎖進行競爭,效率一般
ArrayList:ArrayList是在vector的基礎上去掉了一些同步操作,所以ArrayList是非線程安全的,效率也是三者中最高的。
CopyOnWriteArrayList:CopyOnWriteArrayList算是前兩者的 一個折中的解決方案,通過COW思想在寫操作的時候創建一個數據的副本,然後在副本上進行寫操作。讀操作在原來的數組上進行,二者互不影響,這樣就能夠在寫的同時也可以進行讀操作,在寫操作完成後,就用新的數組替換原來的數組,達到數據更新的目的,這也是一種讀寫分離的思想。由於創建了數據的副本,所以的寫入的數據的時候,其他線程讀的時候可能無法讀取新的數據,這樣就不能保證數據的強一致性,只能保證最終一致性。
五、既然使用了鎖來對寫方法進行了同步為什麽還要創建一個數據副本來進行操作
我個人的 理解是它需要保證數組中的數據在遍歷的時候不能發生改變,所以需要創建一個數據副本。
java數據結構之CopyOnWriteArrayList和CopyOnWriteArraySet