使用CopyOnWriteArrayList時建議使用foreach或iterator

導語

博客太久沒更新了, 18年12月換了工作. 工作比較忙再加上自己有點貪玩. 看到了很多同學後臺的提問和私聊. 找時間會統一回復的哈.

使用CopyOnWriteArrayList時建議使用foreach或iterator

關於CopyOnWriteArrayList就不細講了. 網上資料一大堆. 這裏只是提醒一種場景會導致使用CopyOnWriteArrayList時出現讀取數據的異常情況. 即一條數據被讀取了兩次. 上代碼

/******************************************************
 ****** @ClassName   : ArrayListTest.java                                            
 ****** @author      : milo ^ ^                     
 ****** @date        : 2018 11 16 13:00     
 ****** @version     : v1.0.x                      
 *******************************************************/
public class Test {
    public static void main(String[] args) {
        final CopyOnWriteArrayList list = new CopyOnWriteArrayList();
        list.add("元素1");
        list.add("元素2");

        new Thread(new AddThread(list)).start();
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i) + " 被main線程讀取了.");
        }
    }

    static class AddThread implements Runnable{
        List list;

        AddThread(List list){
            this.list = list;
        }

        @Override
        public void run() {
            System.out.println("添加元素3 start");
            list.add(0,"元素3");
            System.out.println("添加元素3 end");
        }
    }
}

通過線程斷點調試. 讀取玩元素1後將線程掛起, 執行AddThread線程. 最終控制檯輸出

元素1 被main線程讀取了.
添加元素3 start
添加元素3 end
元素1 被main線程讀取了.
元素2 被main線程讀取了.

原因分析

我們使用CopyOnWriteArrayList進行add操作的最後一步是將原引用替換爲新數組(list數據結構是數組)地址.源碼setArray(newElements),此時我們相當於在for循環執行過程中改變了list的數據.
執行第一次for循環時: list= [“元素1”,“元素2”]
執行第二次for循環時: list=[“元素3”,“元素1”,“元素2”]
所以造成我們分別在i=0和i=1時讀到了兩次元素1. 如果元素1的處理是個mq, 且下游沒有做去重處理會直接導致實際業務問題.

解決方案

  1. 避免使用add(int index, E element)方法,改用add(E e)(業務允許情況下), 這樣要麼add操作沒有完成執行原數組遍歷操作, 要麼已經完成且不會改變原數組0~(len-2)的數據(add方法是在數組尾部插入)
  2. 使用iterator或者foreach(基於iterator), 爲什麼iterator可以避免此問題. 看下源碼. iterator是將數組拷貝了一份的.
    public Iterator<E> iterator() {
        return new COWIterator<E>(getArray(), 0);
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章