《Java源碼分析》:CopyOnWriteArrayList/CopyOnWriteArraySet
CopyOnWriteArrayList/CopyOnWriteArraySet的基本思想是一旦對容器有修改,那麼就“複製”一份新的集合,在新的集合上修改,然後將新集合複製給舊的引用。當然了這部分少不了要加鎖。顯然對於CopyOnWriteArrayList/CopyOnWriteArraySet來說最大的好處就是“讀”操作不需要鎖了。
CopyOnWriteArrayList是ArrayList 的一個線程安全的變體,其中所有可變操作(add、set 等等)都是通過對底層數組進行一次新的複製來實現的。 這一般需要很大的開銷,但是當遍歷操作的數量大大超過可變操作的數量時,這種方法可能比其他替代方法更 有效。在不能或不想進行同步遍歷,但又需要從併發線程中排除衝突時,它也很有用。“快照”風格的迭代器方法在創建迭代器時使用了對數組狀態的引用。此數組在迭代器的生存期內不會更改,因此不可能發生衝突,並且迭代器保證不會拋出 ConcurrentModificationException。創建迭代器以後,迭代器就不會反映列表的添加、移除或者更改。在迭代器上進行的元素更改操作(remove、set 和 add)不受支持。這些方法將拋出 UnsupportedOperationException。
這個類確實比較簡單,在一些可變操作下通過加鎖並對底層數組進行一次複製來實現。下面我們就簡單的看下源碼。
CopyOnWriteArrayList構造函數
CopyOnWriteArrayList的底層還是基於數組來實現的,只是此時的數組採用volatile來修飾,來保證內存的一致性,即一個線程對array的修改對另一個線程可見,但不是立即可見。
private transient volatile Object[] array;
- 1
構造函數如下:
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
/**
* Sets the array.
*/
final void setArray(Object[] a) {
array = a;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
即初始對象構造了一個長度爲零的List。
get方法
public E get(int index) {
return get(getArray(), index);
}
final Object[] getArray() {
return array;
}
private E get(Object[] a, int index) {
return (E) a[index];
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
利用的數組的不可變性,get方法的操作數組都是某一時刻array的鏡像。這樣在高併發的情況下get方法和其它線程對該List的訪問(無論是讀操作還是寫操作)都不會產生衝突。
另一些不加鎖的方法contains/indexOf
public int indexOf(Object o) {
Object[] elements = getArray();//得到此時數組的一份鏡像
return indexOf(o, elements, 0, elements.length);
}
private static int indexOf(Object o, Object[] elements,
int index, int fence) {
if (o == null) {
for (int i = index; i < fence; i++)
if (elements[i] == null)
return i;
} else {
for (int i = index; i < fence; i++)
if (o.equals(elements[i]))
return i;
}
return -1;
}
//同indexOf一直
public boolean contains(Object o) {
Object[] elements = getArray();
return indexOf(o, elements, 0, elements.length) >= 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
可變操作(add/set/remove)
下面爲CopyOnWriteArrayList的一些可變操作的內部實現,可變操作都是採用的如下的思想:
1、加鎖
2、將原來的數組copy一份到新數組中,然後修改
3、將舊的引用array指向新數組。
4、釋放鎖
由於源碼都比較簡單好懂,這裏就不解釋了。
/**
* 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();
}
}
/**
* Inserts the specified element at the specified position in this
* list. Shifts the element currently at that position (if any) and
* any subsequent elements to the right (adds one to their indices).
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
newElements[index] = element;
setArray(newElements);
} finally {
lock.unlock();
}
}
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();
}
}
/**
* 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 確保語義
/*
爲了保持“volatile”的語義,任何一個讀操作都應該是一個寫操作的結果,
也就是讀操作看到的數據一定是某個寫操作的結果(儘管寫操作沒有改變數據本身)。
所以這裏即使不設置也沒有問題,僅僅是爲了一個語義上的補充(就如源碼中的註釋所言)。
*/
setArray(elements);//寫回,什麼都沒有改變,爲什麼還要寫回了?這是因爲
}
return oldValue;
} finally {
lock.unlock();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
CopyOnWriteArraySet類分析
上面介紹了CopyOnWriteArrayList類的常見方法,比較簡單。而CopyOnWriteArraySet就更簡單了,因爲,CopyOnWriteArraySet裏面有一個CopyOnWriteArrayList的引用,即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>();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
小結
關於CopyOnWriteArrayList、CopyOnWriteArraySet,我們需要記住以下幾點就好了
1、CopyOnWriteArrayList是ArrayList的線程安全的實現。
2、如何來實現線程安全的呢?底層的數組採用Volatile來聲明的,對於可變操作採用加鎖並對底層的數組進行拷貝一份,在新數組上進行修改,最後將舊引用指向這個新數組即可,對於不可變的操作,利用的數組的不可變性來完成的。