程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> CopyOnWriteArrayList源碼閱讀

CopyOnWriteArrayList源碼閱讀

編輯:C++入門知識

CopyOnWriteArrayList源碼閱讀


java.util.concurrent包中定義常見集合類對應的並發集合類,用於高效處理並發場景,其中CopyOnWriteArrayList對應就是ArrayList。顧名思義CopyOnWrite,寫時拷貝,這裡寫包括對集合類的修改操作,都會創建一個副本。

CopyOnWriteArrayList的實現

類的定義

public class CopyOnWriteArrayList
    implements List, RandomAccess, Cloneable, java.io.Serializable
可以看到沒有繼承任何子類,實現接口和ArrayList類似。

關鍵屬性

/** The lock protecting all mutators */
transient final ReentrantLock lock = new ReentrantLock();
/** The array, accessed only via getArray/setArray. */
private volatile transient Object[] array;
同樣是采用數組方式實現,多了一個volatile聲明,用於保證線程可見性。沒有size聲明表示實際包含元素的大小,多了一個ReentrantLock對象聲明。

常見方法

構造方法

public CopyOnWriteArrayList() {
    setArray(new Object[0]); //默認創建一個空數組
}
public CopyOnWriteArrayList(Collection c) {
	Object[] elements = c.toArray();
	// c.toArray might (incorrectly) not return Object[] (see 6260652)
	if (elements.getClass() != Object[].class)
	    elements = Arrays.copyOf(elements, elements.length, Object[].class);//拷貝一份數組
	setArray(elements);
}
size方法,直接返回數組大小,說明array數組只包含實際大小的空間

public int size() {
        return getArray().length;
}
get方法,和ArrayList中類似,不過沒有index的范圍判斷

public E get(int index) {
        return (E)(getArray()[index]);
}
add方法,可以看到無論是在尾部還是指定位置添加,都有鎖定和解鎖操作,在設置值之前都先將原先數組拷貝一份並擴容至size+1大小。

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);//拷貝array屬性,並擴展為length+1大小
	    newElements[len] = e;
	    setArray(newElements);
	    return true;
	} finally {
	    lock.unlock(); //解鎖
	}
}

public void add(int index, E element) {
	final ReentrantLock lock = this.lock;
	lock.lock();
	try {
	    Object[] elements = getArray();
	    int len = elements.length;
	    if (index > len || index < 0)
		throw new IndexOutOfBoundsException("Index: "+index+
						    ", Size: "+len);
	    Object[] newElements;
	    int numMoved = len - index;
	    if (numMoved == 0) //尾部添加
			newElements = Arrays.copyOf(elements, len + 1);
	    else {
			newElements = new Object[len + 1];
			//elements[0,index) ---> newElements[0,index)
			System.arraycopy(elements, 0, newElements, 0, index);
			//elements[index,len) --> newElements[index+1,len+1)
			System.arraycopy(elements, index, newElements, index + 1,
					 numMoved);
	    }
	    newElements[index] = element;
	    setArray(newElements);
	} finally {
	    lock.unlock();
	}
}
set方法,ArrayList中set方法直接改變數組中對應的引用,這裡需要拷貝數組然後再設置。(else那個分支沒看懂,為什麼值沒有改變還需要設置來保證volatile寫語義)

public E set(int index, E element) {
	final ReentrantLock lock = this.lock;
	lock.lock();
	try {
	    Object[] elements = getArray();
	    Object oldValue = 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 (E)oldValue;
	} finally {
	    lock.unlock();
	}
}
remove(int)方法,和指定位置添加類似,需要拷貝[0,index)和[index+1,len)之間的元素

public E remove(int index) {
	final ReentrantLock lock = this.lock;
	lock.lock();
	try {
	    Object[] elements = getArray();
	    int len = elements.length;
	    Object oldValue = elements[index];
	    int numMoved = len - index - 1;nt
	    if (numMoved == 0) //刪除最後一個元素
			setArray(Arrays.copyOf(elements, len - 1));
	    else {
			Object[] newElements = new Object[len - 1];
			//elements[0,index) --> newElements[0,index)
			System.arraycopy(elements, 0, newElements, 0, index);
			//elements[index+1,len) --> newElements[index,len-1)
			System.arraycopy(elements, index + 1, newElements, index,
					 numMoved);
			setArray(newElements);
	    }
	    return (E)oldValue;
	} finally {
	    lock.unlock();
	}
}
remove(Object)方法,分配一個len-1大小的新數組,遍歷原來數組,如果找到則將原來數組以後的元素拷貝到新數組中並將list設置為新數組,否則直接給新數組賦值上原來數組。

public boolean remove(Object o) {
	final ReentrantLock lock = this.lock;
	lock.lock();
	try {
	    Object[] elements = getArray();
	    int len = elements.length;
	    if (len != 0) {
		// Copy while searching for element to remove
		// This wins in the normal case of element being present
		int newlen = len - 1;
		Object[] newElements = new Object[newlen];

		for (int i = 0; i < newlen; ++i) {
		    if (eq(o, elements[i])) {
			// found one;  copy remaining and exit
			for (int k = i + 1; k < len; ++k)
			    newElements[k-1] = elements[k];
			setArray(newElements);
			return true;
		    } else
			newElements[i] = elements[i];
		}

		// special handling for last cell
		if (eq(o, elements[newlen])) {
		    setArray(newElements);
		    return true;
		}
	    }
	    return false;
	} finally {
	    lock.unlock();
	}
 }

迭代器的實現

ArrayList中迭代器支持fast fail,一旦檢測到遍歷過程中發送了修改則會拋出ConcurrentModificationException;CopyOnWriteArrayList的迭代器由於修改的時候都會重新copy一份數組,因此不存在並發修改問題,也不會拋出ConcurrentModificationException。同樣支持單向和雙向迭代器,其iterator和listIterator方法都是通過內部類COWIterator創建,只是前者返回接口限定為單向迭代Iterator

COWIterator定義

/** 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;
}
iterator和listIterator中會傳遞當前數組的引用和cursor(無參方法為0,有參數方法為對應值)

常見方法

public boolean hasNext() {
    return cursor < snapshot.length;
}
public boolean hasPrevious() {
    return cursor > 0;
}
public E next() {
	if (! hasNext())
        throw new NoSuchElementException();
	return (E) snapshot[cursor++];
}
public E previous() {
	if (! hasPrevious())
        throw new NoSuchElementException();
	return (E) snapshot[--cursor];
}
另外其他add、remove和set修改容器的方法都沒有實現,直接throw new UnsupportedOperationException();

總結

1. CopyOnWriteArrayList的迭代器保留一個執行底層基礎數組的引用,這個數組當前位於迭代器的起始位置,由於基礎數組不會被修改(修改都是復制一個新的數組),因此對其同步只需要保證數組內容的可見性。多個線程可以同時對這個容器進行迭代,而不會彼此干擾或者與修改容器的線程互相干擾。不會拋出CocurrentModificationException,並且返回元素與創建迭代器創建時的元素完全一致,不必考慮之後修改操作帶來影響。

2. 每次修改容器都會復制底層數組,這需要一定開銷,特別是容器規模較大。僅當迭代操作遠遠多於修改操作時,才應該使用CopyOnWriteArrayList。




  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved