ArrayList 线程不安全导致的bug_arraylist 线程不安全替换
问题解决记录
问题背景:
测试人员反馈某银行系统 “商户费率审核”菜单的“批量审核通过”功能,每次对列表数据批量审核时,总会成功审核一部分,遗留一部分。
问题发现:
经排查,发现以下代码:
public void function(){...List<MerchantRateTmp> list; List<String> listM = new ArrayList<>(); if (rowTotal > 0) { list = merchantRateService.getMerchantRateTmpByPage(map); list.parallelStream().forEach(v -> { listM.add(v.getMchntNo()); }); } ... }
对list 列表进行并行流遍历的时候,给一个ArrayList类型的listM列表新增值,再用listM的结果(校验真实性,去重后要审核的商户编号列表)去做审核操作。但每次对相同的商户费率进行审核,listM的结果都不相同,而且listM列表中会出现很多null 值。
问题解析:
- parallelStream 是一种并行流,它利用多线程来加速集合的遍历和操作。
- ArrayList 是一个非线程安全的集合类。(HashMap、HashSet同理)
- 当使用 parallelStream 对 ArrayList 进行操作(添加、删除、修改)时,就会引发线程安全问题。如下:
(1) 数据不一致:多个线程同时修改集合,可能导致数据丢失或覆盖,丢失解释了为什么listM列表中会出现很多null 值,覆盖解释了为什么会批量审核时没有全部审核。
(2) 并发修改异常:在遍历过程中增删集合元素,可能会抛出 ConcurrentModificationException。
问题解决
-
将ArrayList 替换为线程安全的集合类,如 CopyOnWriteArrayList。CopyOnWriteArrayList 通过在写操作时创建副本来保证线程安全。
-
使用 map、filter 等流操作生成新的集合,而不是新建集合赋值。
list.stream().map(MerchantRateTmp::getMchntNo).collect(Collectors.toList());
-
使用 Collections.synchronizedList将 ArrayList 包装为线程安全的集合。
List<String> listM = Collections.synchronizedList(new ArrayList<>());
-
数据量少,放弃parallelStream 并行,直接串行。
list.forEach(v -> listM.add(v.getMchntNo()));
-
…
问题探索:
举例ArrayList 的 add(E e) 方法:
/** * Appends the specified element to the end of this list. * 将指定的元素追加到列表的末尾。 * add() 方法做了如下操作: * 1.检查容量是否足够,如不够将进行扩容,并自增 modCount * 2.将指定的元素追加到列表的末尾 * * @param e element to be appended to this list * @return true (as specified by {@link Collection#add}) */public boolean add(E e) { //确保容量足够,如果不够进行扩容 ensureCapacityInternal(size + 1); // Increments modCount!! //将e存在index为size的位置(即最后一位的下一位置),size++ //我们都知道,++操作不是原子指令,多线程情况下将发生并发问题 elementData[size++] = e; return true;}
[!TIP]
其中 elementData[size++] = e; 这句代码可以分开为
1.elementData[size] = e;
2.size = size + 1;
当多个线程同时执行以上代码就可能出现:
elementData[0]=null,
elementData[1]=null
elementData[0]=null,
elementData[1]=null
elementData[0]=A,
elementData[1]=null
elementData[0]=A,
elementData[1]=null
elementData[0]=B,
elementData[1]=null
elementData[0]=A,
elementData[1]=B
elementData[0]=B,
elementData[1]=null
elementData[0]=A,
elementData[1]=B
elementData[0]=B,
elementData[1]=null
elementData[0]=A,
elementData[1]=B
详细介绍参见
1.https://blog.csdn.net/u012859681/article/details/78206494
2.https://blog.csdn.net/weixin_36378917/article/details/81812210