ArrayList 线程不安全导致的bug

打印 上一主题 下一主题

主题 1847|帖子 1847|积分 5541

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
标题解决纪录

标题配景:

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

经排查,发现以下代码:
  1. public void function(){
  2.         ...
  3.         List<MerchantRateTmp> list;
  4.     List<String> listM = new ArrayList<>();
  5.     if (rowTotal > 0) {
  6.         list = merchantRateService.getMerchantRateTmpByPage(map);
  7.         list.parallelStream().forEach(v -> {
  8.             listM.add(v.getMchntNo());
  9.         });
  10.     }
  11.     ...
  12. }
复制代码
对list 列表举行并行流遍历的时间,给一个ArrayList范例的listM列表新增值,再用listM的结果(校验真实性,去重后要审核的商户编号列表)去做审核操纵。但每次对相同的商户费率举行审核,listM的结果都不相同,而且listM列表中会出现许多null 值。
标题剖析:


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


  • 将ArrayList 更换为线程安全的聚集类,如 CopyOnWriteArrayList。CopyOnWriteArrayList 通过在写操纵时创建副原来保证线程安全。
  • 利用 map、filter 等流操纵天生新的聚集,而不是新建聚集赋值。
    1. list.stream().map(MerchantRateTmp::getMchntNo).collect(Collectors.toList());
    复制代码
  • 利用 Collections.synchronizedList将 ArrayList 包装为线程安全的聚集。
    1. List<String> listM = Collections.synchronizedList(new ArrayList<>());
    复制代码
  • 数据量少,放弃parallelStream 并行,直接串行。
    1. list.forEach(v -> listM.add(v.getMchntNo()));
    复制代码

标题探索:

举例ArrayList 的 add(E e) 方法:
  1. /**
  2. * Appends the specified element to the end of this list.
  3. * 将指定的元素追加到列表的末尾。
  4. * add() 方法做了如下操作:
  5. *     1.检查容量是否足够,如不够将进行扩容,并自增 modCount
  6. *     2.将指定的元素追加到列表的末尾
  7. *
  8. * @param e element to be appended to this list
  9. * @return <tt>true</tt> (as specified by {@link Collection#add})
  10. */
  11. public boolean add(E e) {
  12.     //确保容量足够,如果不够进行扩容
  13.     ensureCapacityInternal(size + 1);  // Increments modCount!!
  14.     //将e存在index为size的位置(即最后一位的下一位置),size++
  15.     //我们都知道,++操作不是原子指令,多线程情况下将发生并发问题
  16.     elementData[size++] = e;
  17.     return true;
  18. }
复制代码
  [!TIP]
  其中 elementData[size++] = e; 这句代码可以分开为
1.elementData[size] = e;
2.size = size + 1;
  当多个线程同时实行以上代码就可能出现:
实行操纵线程一线程二当前步骤结果正确预期往elementData空列表中添加一个值获取size 为0,不用扩容获取size 为0,不用扩容size = 0,
elementData[0]=null,
elementData[1]=nullsize = 0,
elementData[0]=null,
elementData[1]=nullelementData[size] = e;往elementData[0]中添加值Asize = 0,
elementData[0]=A,
elementData[1]=nullsize = 0,
elementData[0]=A,
elementData[1]=null往elementData[0]中添加值Bsize = 0,
elementData[0]=B,
elementData[1]=nullsize = 0,
elementData[0]=A,
elementData[1]=Bsize = size + 1;size+1size = 1,
elementData[0]=B,
elementData[1]=nullsize = 1,
elementData[0]=A,
elementData[1]=Bsize+1size = 2,
elementData[0]=B,
elementData[1]=nullsize = 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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

立山

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表