理解CopyOnWriteArrayList


CopyOnWriteArrayList,顧名思義,Write的時候總是要Copy,也就是說對於任何可變的操作(add、set、remove)都是伴隨復制這個動作的

A thread-safe variant of ArrayList in which all mutative operations (addset, 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 (removeset, and add) are not supported. These methods throw

 UnsupportedOperationException. All elements are permitted, including null.

 

一.初始化

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

    /** The array, accessed only via getArray/setArray. */
    private transient volatile 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;
    }

    /**
     * Creates an empty list.
     */
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

    /**
     * Creates a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection of initially held elements
     * @throws NullPointerException if the specified collection is null
     */
    public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
        if (c.getClass() == CopyOnWriteArrayList.class)
            elements = ((CopyOnWriteArrayList<?>)c).getArray();
        else {
            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);
    }

 

二.如何添加元素

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

添加一個整數2

1、加鎖

2、實例化出一個新的數組,size=原數組+1

3、把原數組的元素復制到新數組中去

4、新數組最后一個位置設置為待添加的元素

5、把Object array引用指向新數組

6、解鎖

 

三。使用


static class ReadThread extends Thread { private List<Integer> list; public ReadThread(List<Integer> list) { this.list = list; } public void run() { for (Integer i : list) { } } } static class WriteThread extends Thread { private List<Integer> list; public WriteThread(List<Integer> list) { this.list = list; } public void run() { for (int i = 0; i < list.size(); i++) { list.remove(i); } } }
        List<Integer> list = new ArrayList<>();

        for (int i = 0; i < 10000; i++) {
            list.add(i);
        }

        ReadThread readThread = new ReadThread(list);
        WriteThread writeThread = new WriteThread(list);
        readThread.start();
        writeThread.start();

Exception in thread "Thread-0" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at com.ys.scs.db.RSAUtils$ReadThread.run(RSAUtils.java:161)

改成vector也不行

Vector雖然是線程安全的,但是只是一種相對的線程安全而不是絕對的線程安全,它只能夠保證增、刪、改、查的單個操作一定是原子的,不會被打斷,

但是如果組合起來用,並不能保證線程安全性。比如就像上面的線程1在遍歷一個Vector中的元素、線程2在刪除一個Vector中的元素一樣,勢必產生並

發修改異常,也就是fail-fast。

CopyOnWriteArrayList的缺點,就是修改代價十分昂貴,每次修改都伴隨着一次的數組復制;隨着CopyOnWriteArrayList中元素的增加,

CopyOnWriteArrayList的修改代價將越來越昂貴,因此,CopyOnWriteArrayList適用於讀操作遠多於修改操作的並發場景中

但同時優點也十分明顯,就是在並發下不會產生任何的線程安全問題,也就是絕對的線程安全,這也是為什么我們要使用CopyOnWriteArrayList的原因。

 

四、總結

CopyOnWriteArrayList這個並發組件,其實反映的是兩個十分重要的分布式理念:

(1)讀寫分離

我們讀取CopyOnWriteArrayList的時候讀取的是CopyOnWriteArrayList中的Object[] array,但是修改的時候,操作的是一個新的Object[] array,讀和寫

操作的不是同一個對象,這就是讀寫分離。這種技術數據庫用的非常多,在高並發下為了緩解數據庫的壓力,即使做了緩存也要對數據庫做讀寫分離,讀

的時候使用讀庫,寫的時候使用寫庫,然后讀庫、寫庫之間進行一定的同步,這樣就避免同一個庫上讀、寫的IO操作太多

(2)最終一致

對CopyOnWriteArrayList來說,線程1讀取集合里面的數據,未必是最新的數據。因為線程2、線程3、線程4四個線程都修改了CopyOnWriteArrayList里面

的數據,但是線程1拿到的還是最老的那個Object[] array,新添加進去的數據並沒有,所以線程1讀取的內容未必准確。不過這些數據雖然對於線程1是不一致

的,但是對於之后的線程一定是一致的,它們拿到的Object[] array一定是三個線程都操作完畢之后的Object array[],這就是最終一致。最終一致對於分布

式系統也非常重要,它通過容忍一定時間的數據不一致,提升整個分布式系統的可用性與分區容錯性。當然,最終一致並不是任何場景都適用的,像火車站

售票這種系統用戶對於數據的實時性要求非常非常高,就必須做成強一致性的。

 

 出處:
http://www.importnew.com/25034.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM