CopyOnWriteArrayList源碼詳解
最近在一個代碼優化中使用到CopyOnWriteArrayList,想起這個java容器知道使用特性是讀寫分離,在每次寫入時都複製一個新的list進行操作,但是沒有具體的看過其源碼細節,於是寫一篇文章來記載下。
demo演示
首先寫一個簡單的程序演示一下,如下:
CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
ExecutorService executorService = Executors.newFixedThreadPool(10);
CountDownLatch countDownLatch = new CountDownLatch(9);
for (int i = 0; i < 9; i++) {
executorService.execute(() -> {
Random random = new Random(System.currentTimeMillis());
list.add(random.nextInt());
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
System.out.println(list.toString());
這裏輸出就隨意了,就不貼出輸出結果了,直接看CopyOnWriteArrayList源碼。
CopyOnWriteArrayList源碼
首先看下CopyOnWriteArrayList的構造方法,進入構造方法。
/**
* Creates an empty list.
*/
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
/**
* Sets the array.
*/
final void setArray(Object[] a) {
array = a;
}
構造方法沒有什麼特別的地方,就是初始化一個空的Object數組。
接下來看add方法,add方法基本就是其關鍵所在,在add方法中實現數組複製寫入,源碼如下:
/**
* 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();
}
}
add方法比較簡單,這裏首先的操作就是對當前方法加鎖。
final ReentrantLock lock = this.lock;
lock.lock();
然後獲取當前數組,然後再進行數組的copy和新值的插入。
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
最後將array重新set入原數組中,這個setArray方法可能是進行兩個數組合並工作,並首先進行了加鎖操作,可以一起看一下。
/**
* Sets the array.
*/
final void setArray(Object[] a) {
array = a;
}
比較簡單,就是進行了array的賦值,那麼能夠肯定的就是remove操作必定也是進行的加鎖操作,這樣除了讀取以外,其他操作都是需要進行鎖的寫入和刪除的。
在新值添加完成後,最後進行unlock操作。
lock.unlock();
這裏想必除了讀取以外,其他的操作都是需要進行加鎖操作的,可以簡單看下get方法。
/**
* {@inheritDoc}
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
操作比較簡單,就是從array中讀取值。
接下來看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) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
remove方法開頭與add相同,也是進行加鎖操作,這樣add、set、remove操作在同時就只有一個操作可以進行,同時也就避免了寫入髒數據問題。
接下來的操作就比較簡單了,進行了一個簡單的數組的copy、value的替換和數組的重新賦值回原數組。
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
這裏就不再對CopyOnWriteArrayList的set或者諸如toString()、sort方法進行講解了,原理都類似,這裏看一個比較有意思的方法,爲subList(int fromIndex, int toIndex),獲取數組中的一段範圍。
/**
* Returns a view of the portion of this list between
* {@code fromIndex}, inclusive, and {@code toIndex}, exclusive.
* The returned list is backed by this list, so changes in the
* returned list are reflected in this list.
*
* <p>The semantics of the list returned by this method become
* undefined if the backing list (i.e., this list) is modified in
* any way other than via the returned list.
*
* @param fromIndex low endpoint (inclusive) of the subList
* @param toIndex high endpoint (exclusive) of the subList
* @return a view of the specified range within this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public List<E> subList(int fromIndex, int toIndex) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (fromIndex < 0 || toIndex > len || fromIndex > toIndex)
throw new IndexOutOfBoundsException();
return new COWSubList<E>(this, fromIndex, toIndex);
} finally {
lock.unlock();
}
}
這個方法直接返回了一個CowList對象,而不是一個list列表,可以看看CowList類。
private static class COWSubList<E>
extends AbstractList<E>
implements RandomAccess
{
private final CopyOnWriteArrayList<E> l;
private final int offset;
private int size;
private Object[] expectedArray;
// only call this holding l's lock
COWSubList(CopyOnWriteArrayList<E> list,
int fromIndex, int toIndex) {
l = list;
expectedArray = l.getArray();
offset = fromIndex;
size = toIndex - fromIndex;
}
// only call this holding l's lock
private void checkForComodification() {
}
// only call this holding l's lock
private void rangeCheck(int index) {
}
public E set(int index, E element) {
}
public E get(int index) {
}
public int size() {
}
public void add(int index, E element) {
}
}
COWSubList方法只是有offset、size等屬性,並不是有真正的list中的值,並有list的相關的方法,那麼在源list發生變化時,這個CowSubList也會發生相應的value的變化,這個在一些特殊的場景下確實比較有作用。
CopyOnWriteArrayList還有一些其他的特殊用法,例如返回listIterator之類的,這些都沒有特別出奇的地方,就不詳細講述了。
CopyOnWriteArrayList用法除了在set、add、remove操作與別的list對應的方法不同外,其他基本倒是類似,因此CopyOnWriteArrayList就講到這裏了。