> 技术文档 > ArrayList 线程不安全导致的bug_arraylist 线程不安全替换

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 值。

问题解析:

  1. parallelStream 是一种并行流,它利用多线程来加速集合的遍历和操作。
  2. ArrayList 是一个非线程安全的集合类。(HashMap、HashSet同理)
  3. 当使用 parallelStream 对 ArrayList 进行操作(添加、删除、修改)时,就会引发线程安全问题。如下:
    (1) 数据不一致:多个线程同时修改集合,可能导致数据丢失或覆盖,丢失解释了为什么listM列表中会出现很多null 值,覆盖解释了为什么会批量审核时没有全部审核。
    (2) 并发修改异常:在遍历过程中增删集合元素,可能会抛出 ConcurrentModificationException。

问题解决

  1. 将ArrayList 替换为线程安全的集合类,如 CopyOnWriteArrayList。CopyOnWriteArrayList 通过在写操作时创建副本来保证线程安全。

  2. 使用 map、filter 等流操作生成新的集合,而不是新建集合赋值。

    list.stream().map(MerchantRateTmp::getMchntNo).collect(Collectors.toList());
  3. 使用 Collections.synchronizedList将 ArrayList 包装为线程安全的集合。

    List<String> listM = Collections.synchronizedList(new ArrayList<>());
  4. 数据量少,放弃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空列表中添加一个值 获取size 为0,不用扩容 获取size 为0,不用扩容 size = 0,
elementData[0]=null,
elementData[1]=null size = 0,
elementData[0]=null,
elementData[1]=null elementData[size] = e; 往elementData[0]中添加值A size = 0,
elementData[0]=A,
elementData[1]=null size = 0,
elementData[0]=A,
elementData[1]=null 往elementData[0]中添加值B size = 0,
elementData[0]=B,
elementData[1]=null size = 0,
elementData[0]=A,
elementData[1]=B size = size + 1; size+1 size = 1,
elementData[0]=B,
elementData[1]=null size = 1,
elementData[0]=A,
elementData[1]=B size+1 size = 2,
elementData[0]=B,
elementData[1]=null size = 2,
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