集合框架(10)-----CopyOnWriteArrayList實現原理及源碼分析

CopyOnWriteArrayList是Java併發包中提供的一個併發容器,它是個線程安全且讀操作無鎖的ArrayList,寫操作則通過創建底層數組的新副本來實現,是一種讀寫分離的併發策略,我們也可以稱這種容器爲"寫時複製器",Java併發包中類似的容器還有CopyOnWriteSet。本文會對CopyOnWriteArrayList的實現原理及源碼進行分析。

實現原理

我們都知道,集合框架中的ArrayList是非線程安全的,Vector雖是線程安全的,但由於簡單粗暴的鎖同步機制,性能較差。而CopyOnWriteArrayList則提供了另一種不同的併發處理策略(當然是針對特定的併發場景)。

很多時候,我們的系統應對的都是讀多寫少的併發場景。CopyOnWriteArrayList容器允許併發讀,讀操作是無鎖的,性能較高。至於寫操作,比如向容器中添加一個元素,則首先將當前容器複製一份,然後在新副本上執行寫操作,結束之後再將原容器的引用指向新容器。
在這裏插入圖片描述

優缺點分析

瞭解了CopyOnWriteArrayList的實現原理,分析它的優缺點及使用場景就很容易了。

優點:

讀操作性能很高,因爲無需任何同步措施,比較適用於讀多寫少的併發場景。Java的list在遍歷時,若中途有別的線程對list容器進行修改,則會拋出ConcurrentModificationException異常。而CopyOnWriteArrayList由於其"讀寫分離"的思想,遍歷和修改操作分別作用在不同的list容器,所以在使用迭代器進行遍歷時候,也就不會拋出ConcurrentModificationException異常了

缺點:

缺點也很明顯,一是內存佔用問題,畢竟每次執行寫操作都要將原容器拷貝一份,數據量大時,對內存壓力較大,可能會引起頻繁GC;二是無法保證實時性,Vector對於讀寫操作均加鎖同步,可以保證讀和寫的強一致性。而CopyOnWriteArrayList由於其實現策略的原因,寫和讀分別作用在新老不同容器上,在寫操作執行過程中,讀不會阻塞但讀取到的卻是老容器的數據。

源碼分析

基本原理了解了,CopyOnWriteArrayList的代碼實現看起來就很容易理解了。

新增操作

public boolean add(E e) {
        //ReentrantLock加鎖,保證線程安全
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            //拷貝原容器,長度爲原容器長度加一
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            //在新副本上執行添加操作
            newElements[len] = e;
            //將原容器引用指向新副本
            setArray(newElements);
            return true;
        } finally {
            //解鎖
            lock.unlock();
        }
    } 

添加的邏輯很簡單,先將原容器copy一份,然後在新副本上執行寫操作,之後再切換引用。當然此過程是要加鎖的。

刪除操作

 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;
            if (numMoved == 0)
                //如果要刪除的是列表末端數據,拷貝前len-1個數據到新副本上,再切換引用
                setArray(Arrays.copyOf(elements, len - 1));
            else {
                //否則,將除要刪除元素之外的其他元素拷貝到新副本中,並切換引用
                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();
        }
    }

刪除操作同理,將除要刪除元素之外的其他元素拷貝到新副本中,然後切換引用,將原容器引用指向新副本。同屬寫操作,需要加鎖。

我們再來看看讀操作,CopyOnWriteArrayList的讀操作是不用加鎖的,性能很高。

public E get(int index) {
        return get(getArray(), index);
    }

直接讀取即可,無需加鎖

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

總結

本文對CopyOnWriteArrayList的實現原理和源碼進行了分析,並對CopyOnWriteArrayList的優缺點也進行了分析(Java併發包中還提供了CopyOnWriteSet,原理類似)。其實所謂併發容器的優缺點,無非是取決於我們在面對特定併發場景時,是否能做出相對合理的選擇和應用。也希望本文能幫助到有需要的童鞋,共勉。

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