1. 程式人生 > >CopyOnWriteArrayList源碼解析

CopyOnWriteArrayList源碼解析

except set nts nbsp color cep 多線程 cnblogs 異常

CopyOnWriteArrayListjava1.5版本提供的一個線程安全的ArrayList變體。

在講解5.1.1ArrayList的時候,有說明ArrayListfail-fast特性,它是指在遍歷過程中,如果ArrayList內容發生過修改,會拋出ConcurrentModificationException

在多線程環境下,這種情況變得尤為突出,參見測試代碼(代碼基於java8):

ArrayList<Integer> list = new ArrayList<>();

ExecutorService executorService 
= Executors.newFixedThreadPool(10); for (int i = 0; i < 100; i++) { // 啟動一個寫ArrayList的線程 executorService.execute(() -> { list.add(1); }); // 啟動一個讀ArrayList的線程 executorService.execute(() -> { for (Integer v : list) { System.out.println(v); } }); }

設想下面兩種處理方式:

1、不使用叠代器形式轉而使用下標來遍歷,這就帶來了一個問題,讀寫沒有分離,寫操作會影響到讀的準確性,甚至導致IndexOutOfBoundsException,比如下面的例子:

上述例子在多線程執行過程中,list.remove(0)會減少listsize,而讀操作使用的是首次遍歷的size,必然會出現嚴重的運行時異常,所以,遍歷下標的方法不可取。

              executorService.execute(() -> {

          list.remove(0);

        });

        executorService.execute(() 
-> {             for (int x = 0, len = list.size(); x < len; x++) {               System.out.println(list.get(x));             }         });

2、不直接遍歷list,而是把list拷貝一份數組,再行遍歷,比如把讀過程修改成下面這樣:

executorService.execute(() -> {

    for (Integer v : list.toArray(new Integer[0])) {

        System.out.println(v);

    }

});

此方法在CopyOnWriteArrayList出現之前較為常見,其本質是把list內容拷貝到了一個新的數組中,CopyOnWriteArrayList也是采取的類似的手段,區別在於,這個例子使用的是CopyOnRead方式,也就是讀時拷貝

下面來介紹下CopyOnWriteArrayList寫時拷貝的實現方式。

1.1.1.1.1 寫時拷貝

寫時拷貝,自然是在做寫操作時,把原始數據拷貝到一個新的數組,涉及到寫操作的是三個方法addremoveset,以add方法為例:

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操作裏,數組都被copy了一份副本,這就是寫時拷貝的原理。

那麽,寫時拷貝和讀時拷貝各有什麽優勢呢?

如果一個list的遍歷操作比寫入操作頻繁,應該使用CopyOnWriteArrayList,反之,則考慮使用讀時拷貝的方式

CopyOnWriteArrayList源碼解析