1. 爲何須要 CopyOnWriteArrayList數組
ArrayList 的內部實現是一個數組, 而且是動態擴容的, 當插入數據時, 先判斷數組是否須要擴容, 若是須要擴容, 則先擴容, 再插入數據, 也就說插入由三步組成併發
1) 檢查是否須要擴容spa
2) 擴容/不擴容線程
3) 數據加入到數組code
代碼以下blog
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
這裏若是出現併發操做, 會有兩個問題ci
1) 若是同時進行擴容, 則有可能出現連續進行兩次擴容的問題, 而實際只須要一次element
2) 若是同時對數組進行賦值, 則有可能第一個賦值元素被覆蓋, 由於可能兩個線程拿到的 size 是同樣的, 他們都填到數組的同一個槽裏rem
再看另外一個 add 操做get
public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
這種狀況下, add 分爲四步
1) 檢查是否須要擴容
2) 擴容
3) 移動數據
4) 插入數據
若是此時有併發的讀取和插入操做, 則有可能出現讀取到的值爲 null 的狀況, 例如 list.get(3) 跟 list.add(3, "new") 同時發生, 原本 list.get(3) 應該拿到 "old" 或者 "new", 如今卻拿到了 null, 這是由於在取值的過程當中正好發生了移動數據, 可是數據又還沒被插入到移動的空槽裏
2. 如何解決這些問題?
一種最簡單的方式是對 ArrayList 的全部行爲所有加鎖, 例如 Collections.synchronizedList(list) 方法, 他會包裝 list, 並對全部操做加鎖
可是這種方式會 block 全部操做, 讀, 寫 都是串行的, 會影響效率
3. CopyOnWriteArrayList 如何解決這些問題
cowlist 的寫操做全都加鎖, 而且在加鎖後會將底層數組複製一份再進行寫操做, 當寫操做完成之後, 整個替換底層數組
1) 使用鎖, 即解決了併發寫的問題
2) 讀操做不加鎖, 效率更高, 讀寫不衝突
3) 寫操做使用副本控制, 解決讀操做會讀到 null 問題, 由於底層數據不會出現有空槽的中間狀態