JUC系列 - CopyOnWriteArrayList源碼分析

java.util.concurrent.CopyOnWriteArrayList 用于替代同步List有滑,在某些情況下它提供了更好的并發(fā)性能,并且在迭代期間不需要對容器進(jìn)行加鎖或復(fù)制。類似地,CopyOnWriteArraySet的作用是替代同步Set草丧。

"寫時復(fù)制(Copy-On-Write)"容器的線程安全性在于,只要正確地發(fā)布一個事實不可變的對象莹桅,那么在訪問該對象時就不再需要進(jìn)一步的同步昌执。在每次修改時,都會創(chuàng)建并重新發(fā)布一個新的容器副本诈泼,從而實現(xiàn)可變性懂拾。

CopyOnWriteArrayList 官方doc說明如下:

A thread-safe variant of ArrayList in which all mutative operations (add, set, and so on) are implemented by making a fresh copy of the underlying array.

This is ordinarily too costly, but may be more efficient than alternatives when traversal operations vastly outnumber mutations, and is useful when you cannot or don't want to synchronize traversals, yet need to preclude interference among concurrent threads. The "snapshot" style iterator method uses a reference to the state of the array at the point that the iterator was created. This array never changes during the lifetime of the iterator, so interference is impossible and the iterator is guaranteed not to throw ConcurrentModificationException. The iterator will not reflect additions, removals, or changes to the list since the iterator was created. Element-changing operations on iterators themselves (remove, set, and add) are not supported. These methods throw UnsupportedOperationException.

All elements are permitted, including null.
Memory consistency effects: As with other concurrent collections, actions in a thread prior to placing an object into a CopyOnWriteArrayList happen-before actions subsequent to the access or removal of that element from the CopyOnWriteArrayList in another thread.

接下來,結(jié)合 **JDK1.7 **源碼來分析CopyOnWriteArrayList內(nèi)部實現(xiàn)機(jī)制铐达。

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    private static final long serialVersionUID = 8673264195747942595L;

    /** The lock protecting all mutators */
    transient final ReentrantLock lock = new ReentrantLock();

    /** The array, accessed only via getArray/setArray. */
    private volatile transient Object[] array;

    /**
     * Gets the array.  Non-private so as to also be accessible
     * from CopyOnWriteArraySet class.
     */
    final Object[] getArray() {
        return array;
    }

    /**
     * Sets the array.
     */
    final void setArray(Object[] a) {
        array = a;
    }

    /**
     * 無參構(gòu)造函數(shù)
     */
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

    /**
     * 帶Collection構(gòu)造函數(shù)
     */
    public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements = c.toArray();
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elements.getClass() != Object[].class)
            elements = Arrays.copyOf(elements, elements.length, Object[].class);
        setArray(elements);
    }
}

CopyOnWriteArrayList內(nèi)部的array數(shù)組被**volatile ** 修飾 能夠保證 array數(shù)組內(nèi)存可見性岖赋,當(dāng)某個線程改變了 array數(shù)組,其它線程能夠立刻看到瓮孙,所以CopyOnWriteArrayList 所有的讀操作都不用加鎖唐断,源碼如下:

    /**以下皆為Read操作**/
    
    /**
     * 獲取指定位置元素
     */
    public E get(int index) {
        return get(getArray(), index);
    }
    
    @SuppressWarnings("unchecked")
    private E get(Object[] a, int index) {
        return (E) a[index];
    }
    
    /**
     * 獲取集合元素個數(shù)
     */
    public int size() {
        return getArray().length;
    }

    /**
     * 判斷集合是否為空
     */
    public boolean isEmpty() {
        return size() == 0;
    }
    /**
     * 判斷集合是否包含某個元素
     */
    public boolean contains(Object o) {
        Object[] elements = getArray();
        return indexOf(o, elements, 0, elements.length) >= 0;
    }

    /**
     * 查找某個元素位置
     */
    public int indexOf(Object o) {
        Object[] elements = getArray();
        return indexOf(o, elements, 0, elements.length);
    }
    
    public int indexOf(E e, int index) {
        Object[] elements = getArray();
        return indexOf(e, elements, index, elements.length);
    }

    private static int indexOf(Object o, Object[] elements,
                               int index, int fence) {
        if (o == null) {
            for (int i = index; i < fence; i++)
                if (elements[i] == null)
                    return i;
        } else {
            for (int i = index; i < fence; i++)
                if (o.equals(elements[i]))
                    return i;
        }
        return -1;
    }
    
    public int lastIndexOf(Object o) {
        Object[] elements = getArray();
        return lastIndexOf(o, elements, elements.length - 1);
    }

    public int lastIndexOf(E e, int index) {
        Object[] elements = getArray();
        return lastIndexOf(e, elements, index);
    }

    private static int lastIndexOf(Object o, Object[] elements, int index) {
        if (o == null) {
            for (int i = index; i >= 0; i--)
                if (elements[i] == null)
                    return i;
        } else {
            for (int i = index; i >= 0; i--)
                if (o.equals(elements[i]))
                    return i;
        }
        return -1;
    }

** volatile** 可以保證 可見性汁汗,但無法保證原子性,CopyOnWriteArrayList 所有的修改操作都需要通過 ReentrantLock 加鎖來保證任意時刻只有一個線程能夠修改CopyOnWriteArrayList 栗涂。
關(guān)于 ReentrantLock 內(nèi)部實現(xiàn)可以參考我的另外一篇文章:
JUC系列 - ReentrantLock源碼解析.

CopyOnWriteArrayList 幾個重要修改操作的源代碼如下:

    /**以下皆為Write操作**/
    
    /**
     * 替換指定位置上的元素
     */
    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();
        }
    }

    /**
     * 向集合添加元素
     */
    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();
        }
    }

    /**
     * 向集合指定位置添加元素
     */
    public void add(int index, E element) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException("Index: "+index+
                                                    ", Size: "+len);
            Object[] newElements;
            int numMoved = len - index;
            if (numMoved == 0)
                newElements = Arrays.copyOf(elements, len + 1);
            else {
                newElements = new Object[len + 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved);
            }
            newElements[index] = element;
            setArray(newElements);
        } finally {
            lock.unlock();
        }
    }

    /**
     * 刪除指定元素
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    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)
                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();
        }
    }

    /**
     * 刪除元素
     */
    public boolean remove(Object o) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            if (len != 0) {
                // Copy while searching for element to remove
                // This wins in the normal case of element being present
                int newlen = len - 1;
                Object[] newElements = new Object[newlen];

                for (int i = 0; i < newlen; ++i) {
                    if (eq(o, elements[i])) {
                        // found one;  copy remaining and exit
                        for (int k = i + 1; k < len; ++k)
                            newElements[k-1] = elements[k];
                        setArray(newElements);
                        return true;
                    } else
                        newElements[i] = elements[i];
                }

                // special handling for last cell
                if (eq(o, elements[newlen])) {
                    setArray(newElements);
                    return true;
                }
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

    /**
     * 刪除指定范圍段所有元素
     * @throws IndexOutOfBoundsException if fromIndex or toIndex out of range
     *         ({@code{fromIndex < 0 || toIndex > size() || toIndex < fromIndex})
     */
    private void removeRange(int fromIndex, int toIndex) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;

            if (fromIndex < 0 || toIndex > len || toIndex < fromIndex)
                throw new IndexOutOfBoundsException();
            int newlen = len - (toIndex - fromIndex);
            int numMoved = len - toIndex;
            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, newlen));
            else {
                Object[] newElements = new Object[newlen];
                System.arraycopy(elements, 0, newElements, 0, fromIndex);
                System.arraycopy(elements, toIndex, newElements,
                                 fromIndex, numMoved);
                setArray(newElements);
            }
        } finally {
            lock.unlock();
        }
    }

    /**
     * 
     */
    public boolean addIfAbsent(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // Copy while checking if already present.
            // This wins in the most common case where it is not present
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = new Object[len + 1];
            for (int i = 0; i < len; ++i) {
                if (eq(e, elements[i]))
                    return false; // exit, throwing away copy
                else
                    newElements[i] = elements[i];
            }
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

可以看到,所有修改操作之前都必須先通過 lock.lock();來加鎖祈争,修改完成后 通過 setArray(newElements); 來發(fā)布新的array數(shù)組斤程,最后通過 lock.unlock();釋放鎖。

應(yīng)用場景

顯然菩混,每次修改容器時都會復(fù)制底層數(shù)組忿墅,這需要一定的開銷,特別是當(dāng)容器的規(guī)模較大時沮峡。僅當(dāng)?shù)僮鬟h(yuǎn)遠(yuǎn)多于修改操作時疚脐,才應(yīng)該使用"寫時復(fù)制(Copy-On-Write)"容器。例如事件通知系統(tǒng):在分發(fā)通知時需要迭代已注冊監(jiān)聽器鏈表邢疙,并調(diào)用每一個監(jiān)聽器棍弄,在大多數(shù)情況下,注冊和注銷事件監(jiān)聽器的操作遠(yuǎn)遠(yuǎn)少于接收事件通知的操作疟游。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末呼畸,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子颁虐,更是在濱河造成了極大的恐慌蛮原,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,423評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件另绩,死亡現(xiàn)場離奇詭異儒陨,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)笋籽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,147評論 2 385
  • 文/潘曉璐 我一進(jìn)店門蹦漠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人干签,你說我怎么就攤上這事津辩。” “怎么了容劳?”我有些...
    開封第一講書人閱讀 157,019評論 0 348
  • 文/不壞的土叔 我叫張陵喘沿,是天一觀的道長。 經(jīng)常有香客問我竭贩,道長蚜印,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,443評論 1 283
  • 正文 為了忘掉前任留量,我火速辦了婚禮窄赋,結(jié)果婚禮上哟冬,老公的妹妹穿的比我還像新娘。我一直安慰自己忆绰,他們只是感情好浩峡,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,535評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著错敢,像睡著了一般翰灾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上稚茅,一...
    開封第一講書人閱讀 49,798評論 1 290
  • 那天纸淮,我揣著相機(jī)與錄音,去河邊找鬼亚享。 笑死咽块,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的欺税。 我是一名探鬼主播侈沪,決...
    沈念sama閱讀 38,941評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼魄衅!你這毒婦竟也來了峭竣?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,704評論 0 266
  • 序言:老撾萬榮一對情侶失蹤晃虫,失蹤者是張志新(化名)和其女友劉穎皆撩,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哲银,經(jīng)...
    沈念sama閱讀 44,152評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡扛吞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,494評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了荆责。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片滥比。...
    茶點(diǎn)故事閱讀 38,629評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖做院,靈堂內(nèi)的尸體忽然破棺而出盲泛,到底是詐尸還是另有隱情,我是刑警寧澤键耕,帶...
    沈念sama閱讀 34,295評論 4 329
  • 正文 年R本政府宣布寺滚,位于F島的核電站,受9級特大地震影響屈雄,放射性物質(zhì)發(fā)生泄漏村视。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,901評論 3 313
  • 文/蒙蒙 一酒奶、第九天 我趴在偏房一處隱蔽的房頂上張望蚁孔。 院中可真熱鬧奶赔,春花似錦、人聲如沸杠氢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鼻百。三九已至笛钝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間愕宋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,978評論 1 266
  • 我被黑心中介騙來泰國打工结榄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留中贝,地道東北人。 一個月前我還...
    沈念sama閱讀 46,333評論 2 360
  • 正文 我出身青樓臼朗,卻偏偏與公主長得像邻寿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子视哑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,499評論 2 348

推薦閱讀更多精彩內(nèi)容