CopyOnWriteArrayList的設計思想很是簡單,但在設計層面有一些小問題須要注意。java
JDK版本:oracle java 1.8.0_102git
原本不想寫的,可是github上CopyOnWriteArrayList的code results也有165k,爲了流量仍是寫一寫吧。github
看兩個方法你就懂了。數組
public E get(int index) {
return get(getArray(), index);
}
final Object[] getArray() {
return array;
}
複製代碼
get()方法直接調用內部的getArray()方法,而getArray()方法則直接返回成員變量array。安全
我沒明白爲何要再封裝一層,而不是直接訪問。數據結構
array指向一個數組,是CopyOnWriteArrayList的內部數據結構:併發
private transient volatile Object[] array;
複製代碼
敲黑板!!!oracle
**array是一個volatile變量,**其讀、寫操做具備Happends-Before關係。具體來說,線程W1經過set()方法「修改」集合後,線程R1能馬上經過get()方法獲得array的最新值。app
你能夠理解爲volatile變量的讀、寫是原子的,不過,我更但願你能從順序和可見性的角度理解理解volatile、鎖等具備偏序關係的操做。volatile的原理和用法見volatile關鍵字的做用、原理。性能
重點是set()方法:
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();
}
}
final void setArray(Object[] a) {
array = a;
}
複製代碼
set()方法也很簡單,兩個要點:
按照獨佔鎖的思路,僅僅給寫線程加鎖是不行的,會有讀、寫線程的競爭問題。可是get()中明明沒有加鎖,爲何也沒有問題呢?
經過加鎖,保證同一時間最多隻有一個寫線程W1進入try block;假設要設置的值與舊值不一樣。9-10行首先將數據複製一份(此時,沒有其餘寫線程能進入try block修改集合),11行在副本上修改相應元素,12行修改array引用。array是volatile變量,因此寫的最新值對其餘讀線程、寫線程都是可見的。
這就是所謂的「寫時複製
」。
實際上,15行的volatile寫是多餘的。這只是爲了能從代碼裏理解到volatile寫的語義,並沒必要要的保證什麼——不過這種考慮也是不恰當的,反而使代碼迷惑。一個相似的例子是addIfAbsent():
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
private boolean addIfAbsent(E e, Object[] snapshot) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] current = getArray();
int len = current.length;
if (snapshot != current) {
// Optimize for lost race to another addXXX operation
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
if (current[i] != snapshot[i] && eq(e, current[i]))
return false;
if (indexOf(e, current, common, len) >= 0)
return false;
}
Object[] newElements = Arrays.copyOf(current, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
複製代碼
基本思想相同,1七、19行都是直接返回,並無作多餘的「volatile寫」。
在網上搜的話,還有不少其餘觀點。若是你認爲個人觀點是錯誤的,歡迎交流。
addIfAbsent的編碼風格跟set()區別很大,不像一我的寫的。須要認識到,JDK是一個發展、變化的產品,一個包、甚至一個類均可能不是同一我的、同一段時間寫的,編碼風格、設計思想可能發生變化;更不要假定JDK的實現必定是對的(固然,絕大部分時候是對的),要基於正確的邏輯去分析,再作判斷。
TODO 20171024
看起來,若是不給set加鎖,彷佛併發性能更高,一致性也沒有削弱多少。未解決,歡迎交流。
最後總結CopyOnWriteArrayList的設計思想:
本文連接:源碼|併發一枝花之CopyOnWriteArrayList
做者:猴子007
出處:monkeysayhi.github.io
本文基於 知識共享署名-相同方式共享 4.0 國際許可協議發佈,歡迎轉載,演繹或用於商業目的,可是必須保留本文的署名及連接。