java.util.concurrent包系列文章
JUC—ThreadLocal源碼解析(JDK13)
JUC—ThreadPoolExecutor線程池源碼解析(JDK13)
JUC—各種鎖(JDK13)
JUC—原子類Atomic*.java源碼解析(JDK13)
JUC—CAS源碼解析(JDK13)
JUC—ConcurrentHashMap源碼解析(JDK13)
JUC—CopyOnWriteArrayList源碼解析(JDK13)
JUC—併發隊列源碼解析(JDK13)
JUC—多線程下控制併發流程(JDK13)
JUC—AbstractQueuedSynchronizer解析(JDK13)
一、CopyOnWrite*系列
CopyOnWriteArrayList和CopyOnWriteArraySet也是常用的併發集合類。他們支持併發的讀寫。線程安全的。不過有它的缺點。
本篇就分析下CopyOnWriteArrayList的原理和源碼。CopyOnWriteArraySet也是類似的。
二 、CopyOnWriteArrayList
CopyOnWriteArrayList誕生自JDK1.5,是爲了取代Vector和SynchronizedList的,就和ConcurrentHashMap取代SynchronizedMap一樣。Vector和SynchronizedList鎖的粒度太大,併發效率低。並且迭代時無法編輯。
適用場景:
- 多讀少寫的情況,比如系統緩存的白名單。偶爾更新一次,讀卻特別多。
讀寫規則:
- 讀取完全不用加鎖,寫入也不會阻塞讀操作。只有寫入與寫入之間需要同步等待。
看一份代碼:
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
// CopyOnWriteArrayList生成迭代器。迭代器裏的數組就是生成迭代器時list的數組。
// 後續的對list的修改時對這個數組的數據不產生影響。修改是在新的內存中新copy了一個同樣的數組。
// 修改完成後再把list數組指向這個新數組。
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println("list is " + list);
String next = iterator.next();
System.out.println("next is " + next);
if (next.equals("B")) {
list.remove("D");
}
if (next.equals("C")) {
list.add("E");
}
}
}
運行結果:
list is [A, B, C, D]
next is A
list is [A, B, C, D]
next is B
list is [A, B, C]
next is C
list is [A, B, C, E]
next is D
這就是CopyOnWrite的意思。創建新副本,讀寫分離。add的時候會在新內存copy一份原數組,add完成之後讓CopyOnWriteArrayList的array指向新數組,舊的就會被回收了。實現了讀寫不需要同步。寫與寫才需要同步。
上面把CopyOnWriteArrayList換成ArrayList是不行的,ArrayList不支持在迭代時操作數組。
ArrayList報錯原因:
看看ArrayList裏面迭代器的屬性和方法
public E next() {
// 獲取下一個元素之前會判斷當前數組長度是否變化了
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
// expectedModCount是生成迭代器時modCount賦給expectedModCount
int expectedModCount = modCount;
final void checkForComodification() {
// modCount每次修改就+1,
// expectedModCount是生成迭代器時modCount賦給expectedModCount
if (modCount != expectedModCount)
// 如果數組長度變化了,說明被修改了,直接報錯
throw new ConcurrentModificationException();
}
CopyOnWriteArrayList的add方法
public boolean add(E e) {
// 上鎖,寫必須同步
synchronized (lock) {
Object[] es = getArray();
int len = es.length;
// 複製了一個新數組,並且長度是原來的+1
es = Arrays.copyOf(es, len + 1);
// 新加的元素添加到數組最後
es[len] = e;
// add完成後,把array指向新數組
setArray(es);
return true;
}
}
final void setArray(Object[] a) {
array = a;
}
CopyOnWriteArrayList的get方法
可以發現get方法沒有任何的加鎖行爲
public E get(int index) {
return elementAt(getArray(), index);
}
static <E> E elementAt(Object[] a, int index) {
return (E) a[index];
}
初始化方法與鎖
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
private static final long serialVersionUID = 8673264195747942595L;
final transient Object lock = new Object();
private transient volatile Object[] array;
final Object[] getArray() {
return array;
}
final void setArray(Object[] a) {
array = a;
}
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
}
CopyOnWriteArrayList的缺點:
- 迭代期間的數據可能過時了的
- 內存佔用更大,因爲是copy一份數組嘛
- 我的公衆號:Coding摳腚
- 偶爾發發自己最近學到的乾貨。學習路線,經驗,技術分享。技術問題交流探討。