CopyOnWriteArrayList源碼分析

來源:互聯網
上載者:User
概述

Java自1.5後提供了兩個寫時複製的容器,分別是CopyOnWriteArrayList和CopyOnWriteArraySet。其思路就是在執行會改變底層資料的結構時,首先加鎖,然後複製得到一個新的資料,在這個資料上做修改,最後再將原來的資料引用指向這個新的資料,最後釋放鎖;而讀操作則不需要修改。這是一種讀寫分離的思想,讀和寫不同的容器,讀的是舊容器,寫的是新容器。
由於CopyOnWriteArrayList的實現原理和CopyOnWriteArraySet類似,所以就以CopyOnWriteArrayList拋個磚。
下圖是CopyOnWriteArrayList的類繼承關係,如下圖:

可以發現實現了List介面,直接繼承了Object類,而沒有像很多List一樣繼承AbstractList類。CopyOnWriteArrayList是一個自動擴容的List,允許任何元素,包括null。 源碼分析 重要欄位

CopyOnWriteArrayList名中含有“ArrayList”,所以其內部是基於數組實現的一個列表。為了使讀寫分離,所以不能在類自身上鎖,內部含有一把鎖,欄位如下:

 /** The lock protecting all mutators */    final transient ReentrantLock lock = new ReentrantLock();    /** The array, accessed only via getArray/setArray. */    private transient volatile Object[] array;
add(E e)方法

下面看一下添加一個元素的方法:

public boolean add(E e) {        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();        }    }

從上面的代碼可以看到添加一個元素add方法的執行流程:
1. 對Lock加鎖
2. 一旦得到鎖後,根據舊資料建立新資料
3. 更改新資料
4. 將引用指向新資料

上面代碼中的setArray()方法就是將array引用指向新建立的數組,該方法的實現如下:

final void setArray(Object[] a) {        array = a;    }
remove(int index)方法

下面再看一下rmeove(int index)方法,如果index超出了索引,那麼會拋出IndexOutBoundsException,實現如下:

 public E remove(int index) {        final ReentrantLock lock = this.lock;        //加鎖        lock.lock();        try {            //得到舊資料            Object[] elements = getArray();            int len = elements.length;            //獲得元素,該方法可能會拋出IndexOutBoundsException            E oldValue = get(elements, index);            //計算左移資料個數            int numMoved = len - index - 1;            //如果不需要左移,更新引用指向            if (numMoved == 0)                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();        }    }

從上面的代碼可以看到,remove()方法的執行流程和add()相同,都是:
1. 加鎖
2. 複製舊資料,修改新資料
3. 將引用指向新資料
4. 釋放鎖 寫方法總結

CopyOnWriteArrayList中的add、set、remove等方法會修改底層數組,所以當執行這些方法時會首先擷取Lock鎖,也就意味著同一時刻只能有一個線程在修改資料。 get(int index)方法

下面看一下get()方法的實現:

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

可以看到該方法沒有加鎖。 size()方法

下面是size()方法的實現:

public int size() {        return getArray().length;    }
讀方法總結

CopyOnWriteArrayList中的get、size、isEmpty等方法不會修改底層資料,所以當執行這些方法時不會擷取Lock鎖,直接對舊資料進行讀取。 拓展

Java中提供了CopyOnWriteArrayList和CopyOnWriteArraySet,而沒有提供CopyOnWriteMap,但是經過上面的源碼分析,我們已經知道了COW的原理,那就是寫時加鎖,讀時不加鎖。按照這種思想實現了一個CopyOnWriteMap,如下:

public class CopyOnWriteMap<K, V> implements Map<K, V>{    private volatile Map<K, V> internalMap;    private ReentrantLock lock=new ReentrantLock();    public CopyOnWriteMap() {        internalMap = new HashMap<K, V>();    }    //。。。帶參數的構造方法,配置HashMap    public V put(K key, V value) {        final ReentrantLock lock = this.lock;        lock.lock();        try {            Map<K,V> newMap=new HashMap(internalMap);            V v=newMap.put(key,value);            internalMap=newMap;            return v;        } finally {            lock.unlock();        }    }    public V remove(Object key) {        final ReentrantLock lock = this.lock;        lock.lock();        try {            Map<K, V> newMap = new HashMap<K, V>(internalMap);            V val = newMap.remove(key);            internalMap = newMap;            return val;        } finally {            lock.unlock();        }    }    public int size() {        return internalMap.size();    }    public boolean isEmpty() {        return internalMap.isEmpty();    }    public V get(Object key) {        return internalMap.get(key);    }    ...其他方法}

CopyOnWriteMap實現了Map介面,Map中的每個方法利用內部的HashMap實現,對於寫操作對Lock加鎖,而讀操作則不加鎖。 總結 使用情境

CopyOnWrite並發容器用於讀多寫少的並發情境。比如白名單,黑名單,商品類目的訪問和更新情境,假如有一個搜尋網站,使用者在這個網站的搜尋方塊中,輸入關鍵字搜尋內容,但是某些關鍵字不允許被搜尋。這些不能被搜尋的關鍵字會被放在一個黑名單當中,黑名單每天晚上更新一次。當使用者搜尋時,會檢查當前關鍵字在不在黑名單當中,如果在,則提示不能搜尋。 缺點

CopyOnWrite容器有很多優點,不過也有兩個缺點:
1. 記憶體佔用問題: 由於每次寫操作都會複製出一個對象出來,如果舊資料已經很大,那麼複製的資料也會很大,可能會導致過多的GC
2. 資料一致性問題: COW容器只能確保最終資料的一致性,不能保證即時資料一致性。如果你希望寫入的資料,馬上能被讀到,請不要使用COW容器。 原理

讀寫分離,寫時加鎖,讀時不加鎖。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.