jdk源碼解析三之CopyOnWriteArrayList

CopyOnWriteArrayList

寫入時複製,只要正確發佈一個事實不可變對象,在訪問該對象時就不再需要進一步同步,在每次修改時,都會創建並重新發佈一個新的容器副本,從而實現可變性.
使用場景:迭代>修改,事件通知系統(註冊和註銷事件監聽器操作少於接收事件通知的操作)

   //構造時,初始化容量爲0的Object數組
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

add

    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 {
            //解鎖
            lock.unlock();
        }
    }

remove

    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);
            
            int numMoved = len - index - 1;
            //最後一個數組索引,則直接copy
            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, len - 1));
            else {
                //copy2次
                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();
        }
    }

get

   public E get(int index) {
        return get(getArray(), index);
    }
    private E get(Object[] a, int index) {
        return (E) a[index];
    }

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();
        }
    }

總結

應用場景:讀多寫少
如:黑名單,監聽器

讀上沒加鎖,所以支持大量併發,但涉及修改的時候,有2個數組副本,當數組過大,則造成多餘的內存消耗.

CopyOnWrite容器只能保證數據的最終一致性,不能保證數據的實時一致性。之所以只能保證最終一致,是因爲每次涉及到修改都會copy一個副本然後回寫,最終的結果是一致的,但是在copy途中如果有讀操作,那麼就會造成數據不一致問題.

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