导语
博客太久没更新了, 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);
}
版权声明:本文为shangmingtao原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。