Java CopyOnWriteArrayList

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 問題, 由於底層數據不會出現有空槽的中間狀態

相關文章
相關標籤/搜索