立山 发表于 2025-4-9 15:43:18

ArrayList 线程不安全导致的bug

标题解决纪录

标题配景:

测试职员反馈某银行体系 “商户费率审核”菜单的“批量审核通过”功能,每次对列表数据批量审核时,总会成功审核一部分,遗留一部分。
标题发现:

经排查,发现以下代码:
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 <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
    //确保容量足够,如果不够进行扩容
    ensureCapacityInternal(size + 1);// Increments modCount!!
    //将e存在index为size的位置(即最后一位的下一位置),size++
    //我们都知道,++操作不是原子指令,多线程情况下将发生并发问题
    elementData = e;
    return true;
}
   [!TIP]
其中 elementData = e; 这句代码可以分开为
1.elementData = e;
2.size = size + 1;
当多个线程同时实行以上代码就可能出现:
实行操纵线程一线程二当前步骤结果正确预期往elementData空列表中添加一个值获取size 为0,不用扩容获取size 为0,不用扩容size = 0,
elementData=null,
elementData=nullsize = 0,
elementData=null,
elementData=nullelementData = e;往elementData中添加值Asize = 0,
elementData=A,
elementData=nullsize = 0,
elementData=A,
elementData=null往elementData中添加值Bsize = 0,
elementData=B,
elementData=nullsize = 0,
elementData=A,
elementData=Bsize = size + 1;size+1size = 1,
elementData=B,
elementData=nullsize = 1,
elementData=A,
elementData=Bsize+1size = 2,
elementData=B,
elementData=nullsize = 2,
elementData=A,
elementData=B 具体介绍拜见
1.https://blog.csdn.net/u012859681/article/details/78206494
2.https://blog.csdn.net/weixin_36378917/article/details/81812210

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: ArrayList 线程不安全导致的bug