Java併發集合之CopyOnWriteArrayList源碼解析

目錄

1.CopyOnWriteArrayList

1.1整體架構

1.2新增方法源碼分析

1.3刪除方法的源碼分析

1.4迭代方法分析


1.CopyOnWriteArrayList


1.1整體架構

  • 通過鎖+數組拷貝+volatile關鍵字保證了線程安全
  • 每次操作都會拷貝一份數組,然後在新數組上進行操作,操作成功後在賦值回去
//一旦數組被改變就可以知道
private transient volatile Object[] array;

1.2新增方法源碼分析

// 添加元素到數組尾部
public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    // 加鎖
    lock.lock();
    try {
        // 得到所有的原數組
        Object[] elements = getArray();
        int len = elements.length;
        // 拷貝到新數組裏面,新數組的長度是 + 1 的,因爲新增會多一個元素
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        // 在新數組中進行賦值,新元素直接放在數組的尾部
        newElements[len] = e;
        // 替換掉原來的數組
        setArray(newElements);
        return true;
    // finally 裏面釋放鎖,保證即使 try 發生了異常,仍然能夠釋放鎖   
    } finally {
        lock.unlock();
    }
}

整個過程:加鎖,獲取數組,數組拷貝,新數組上操作,替換掉原數組,解鎖

如果只是改變了數組的值是無法觸發可見性的,只有拷貝新的數組,會有新的地址然後進行賦值,纔可以可見!

...
// len:數組的長度、index:插入的位置(索引)
// 因爲是新增索引len也可以等價爲最終的數組索引
int numMoved = len - index;
// 如果要插入的位置正好等於數組的末尾,直接拷貝數組即可
if (numMoved == 0)
    newElements = Arrays.copyOf(elements, len + 1);
else {
// 如果要插入的位置在數組的中間,就需要拷貝 2 次
// 第一次從 0 拷貝到 index-1。
// 第二次從 index+1 拷貝到末尾。
    newElements = new Object[len + 1];
    System.arraycopy(elements, 0, newElements, 0, index);//0爲起始位置,index在這是拷貝的數量
    System.arraycopy(elements, index, newElements, index + 1,
         numMoved);//index+1爲拷貝的起始位置,numMoved也是拷貝的數量
}
// index 索引位置的值是空的,直接賦值即可。
newElements[index] = element;
// 把新數組的值賦值給數組的容器中
setArray(newElements);
...

指定位置插入:先加鎖,判斷是否是尾部插入,如果是尾部插入只需要直接拷貝一次,在進行賦值,新舊數組進行替換,解鎖

1.3刪除方法的源碼分析

// 刪除某個索引位置的數據
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);
        //len-1才爲索引位置,在減去index即爲要移動的元素個數
        int numMoved = len - index - 1;
        // 如果要刪除的數據正好是數組的尾部,直接刪除
        if (numMoved == 0)
            setArray(Arrays.copyOf(elements, len - 1));
        else {
            // 如果刪除的數據在數組的中間,
            // 從 0 拷貝到 index-1
            // 從 index-1 拷貝到數組尾部
            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();
    }
}

先獲取鎖,在獲取數組,在得到新值需要進行返回,在通過movedNum來確定是否是尾部刪除,如果是尾部刪除只需要拷貝一次,如果不是尾部刪除先從0拷貝到index-1,在從index+1拷貝到最後,一共兩次拷貝,新舊數組進行替換,解鎖,返回刪除的值

ps:因爲Arrays.copyOf方法只能從頭開始拷貝,而Arrays.copyOfRange可以進行指定範圍拷貝

批量刪除:會把每一個未刪除元素按順序的放入到新數組

1.4迭代方法分析

//CopyOnWriteArrayList 類獲取迭代器
public Iterator<E> iterator() {
    return new COWIterator<E>(getArray(), 0);
}
//CopyOnWriteArrayList 類的靜態內部類
private static class COWIterator<E> implements ListIterator<E> {
    private final Object[] snapshot;
    private int cursor;
    //初始化只是增加了舊數組的引用而已,因爲一定不會對舊數組進行修改的
    private COWIterator(Object[] elements, int initialCursor) {
        cursor = initialCursor;
        snapshot = elements;
    }
}

因爲永遠不會對舊數組進行更改,每次只是將引用重新指向新數組而已,

迭代進行初始化的時候是增加對舊數組的引用,如果進行更改了那麼只是遍歷出來是舊版本而已。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章