導語
博客太久沒更新了, 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, 且下游沒有做去重處理會直接導致實際業務問題.
解決方案
- 避免使用
add(int index, E element)
方法,改用add(E e)
(業務允許情況下), 這樣要麼add操作沒有完成執行原數組遍歷操作, 要麼已經完成且不會改變原數組0~(len-2)的數據(add方法是在數組尾部插入) - 使用iterator或者foreach(基於iterator), 爲什麼iterator可以避免此問題. 看下源碼. iterator是將數組拷貝了一份的.
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}