抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

并发修改异常问题

  • 遍历集合的同时又存在增删集合元素的行为时可能出现业务异常,这种现象被称之为并发修改异常问题。

假设有个需求:

  • 现在购物车中存储了如下商品:JAVA入门,宁夏枸杞,黑枸杞,人字拖,特级枸杞,枸杞子。现在用户不想买枸杞了,选择了批量删除。

分析:

  1. 后台用ArrayList集合表示购物车,存储这些商品名。
  2. 遍历集合中的每个数据,只要这个数据包含了“枸杞“则删除它。
  3. 输出集合看是否已经成功删除了全部枸杞数据了。

按照步骤的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ArrayList<String> list = new ArrayList<>();
list.add("JAVA入门");
list.add("宁夏枸杞");
list.add("黑枸杞");
list.add("人字拖");
list.add("特级枸杞");
list.add("枸杞子");
System.out.println(list);

for (int i = 0; i < list.size(); i++) {
String name = list.get(i);
if (name.contains("枸杞")){
list.remove(name);
}
}

System.out.println(list);

这么做代码看似没问题,实则得到的结果却是:
alt text
没有删除干净,实际是因为删除的时候,索引发生了变动。

解决方案如下:

1
2
3
4
5
6
7
for (int i = 0; i < list.size(); i++) {
String name = list.get(i);
if (name.contains("枸杞")){
list.remove(name);
i--; // 增加一步i--,回到正确的索引位置
}
}

或者可以倒着索引,即:

1
2
3
4
5
6
7
for (int i = list.size() - 1; i >= 0; i--) { //这一步倒着来
String name = list.get(i);
if (name.contains("枸杞")){
list.remove(name);
}
}
System.out.println(list);

以上两种方案都有一个前提,即集合支持索引,如果不支持索引呢,则需要更换方法
如:

  • 用迭代器的方法操作
    1
    2
    3
    4
    5
    6
    7
    8
    9
        Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()){
    String name = iterator.next();
    if (name.contains("枸杞")){
    // list.remove(name);// 这一步这么写会抛异常
    iterator.remove(); // 要用迭代器自己的删除方法
    }
    }
    System.out.println(list);

注意:这个问题用增强for或者lambda都无法解决,这两只适合用作遍历

得出结论:

  1. 如果集合支持索引,可以使用for循环遍历,每删除数据后做i–;或者可以倒着遍历
  2. 可以使用迭代器遍历,并用迭代器提供的删除方法删除数据。
  3. 增强for循环/Lambda遍历均不能解决并发修改异常问题,因此它们只适合做数据的遍历,不适合同时做增删操作。

会特此记录这个BUG源自实习的时候做的第一个需求,大概率就是因为这个BUG,直至离职的时候都未能解决,顿悟的时候已经无法运行校验自己的代码了。

评论