【Java】深入理解并发修改异常及避雷方式

唠嗑部分

在切入正题之前我们先聊一聊java语言中list集合的几种语法层面的遍历方式

第一种:传统for循环

for (int i = 0; i < list.size(); i++) {
    
    
    System.out.println(list.get(i));
}

第二种:增强for循环

for (String value : list) {
    
    
    System.out.println(value);
}

第三种:forEach

list.forEach(System.out::println);

第四种:stream流

list.stream().forEach(System.out::println);

还有一种,iterator

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    
    
    System.out.println(iterator.next());
}

其实啊,第三种、第四种在遍历集合时,也会使用iterator迭代器去处理,但是,iterator这种方式如果使用不当,就可能会导致并发修改异常,那么今天小白就来说一说日常开发中如何去避免这个问题

并发修改异常长这个样子哈,以后见到了可别说不认识

ConcurrentModificationException
image-20230508102400637

言归正传

案发现场:请看源代码

List<String> list = new ArrayList<>();
list.add("11");
list.add("12");
list.add("13");
list.add("14");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    
    
    String value = iterator.next();
    if ("12".equals(value)) {
    
    
        list.remove(value);
    }
}

以上这个代码,就出现了我们说的并发修改异常

image-20230508102825632

如图所示哈,从控制台我们可以看出,罪魁祸首在程序的22行,那么为什么会抛出这个异常呢?我们来一起探究一下

首先我们找到Itr类,它是匿名在ArrayList中的一个类,实现了Iterator接口,list.iterator()返回的就是这个类的实例

image-20230508103135272

请看上图,类很简单,构造器没有做任何事情,三个成员变量

cursor:返回下一个元素的索引,int初始化为0

lastRet:返回最后一个元素的索引,没有则返回-1

看849行,modCount是ArrayList中的一个成员变量,表示对集合的修改次数,将其赋给expectedModCount变量

那么看重点,在我们的遍历代码中,似乎只有两个方法,我们分别贴一下吧

hasNext():size是集合中元素的数量,cursor初始为0

public boolean hasNext() {
    
    
    return cursor != size;
}

next()

public E next() {
    
    
    checkForComodification();
    int i = cursor;
    if (i >= size)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    cursor = i + 1;
    return (E) elementData[lastRet = i];
}

看上面这个方法,在获取元素的最前面,有个checkForComodification(),那这个是做什么的呢?

final void checkForComodification() {
    
    
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

总算是看到ConcurrentModificationException了,原来是在这里抛出的,这也就与上面22行抛出异常吻合了,就在next()这个的方法

它会去判断一下集合的修改次数是否与expectedModCount(期望的修改次数)一致,如果不一致,就抛出异常

expectedModCount变量是跌代器的实例变量,出现不一致,难不成是modCount变了?

那是不是我们可以这样理解,在迭代过程中,modCount不能变,或者是始终让expectedModCount与modCount一致

上面使用list.remove()方法,是不是会修改modCount的?是的

image-20230508104459340

看上图,remove一次,modCount会++,那么下次next()的时候,expectedModCount与modCount就不一致了

不只是remove,add也会,请自行研究

那么问题找到了,要如何解决呢,按照我们上述分析的思路,有三种解决办法?

1、modCount不能变

这个简单,在遍历过程中,不对集合中的元素操作就完了

2、modCount++之后,不让进行下次的next()

案例代码改造:手动break

List<String> list = new ArrayList<>();
list.add("11");
list.add("12");
list.add("13");
list.add("14");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    
    
    String value = iterator.next();
    if ("12".equals(value)) {
    
    
        list.remove(value);
        break;
    }
}
System.out.println(list);

3、始终让expectedModCount与modCount一致

我们对集合中的元素进行了操作,modCount肯定会变,expectedModCount这个变量也没有暴露方法,不能直接修改,其实啊Iterator也有一个remove方法

public void remove() {
    
    
    if (lastRet < 0)
        throw new IllegalStateException();
    checkForComodification();

    try {
    
    
        ArrayList.this.remove(lastRet);
        cursor = lastRet;
        lastRet = -1;
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
    
    
        throw new ConcurrentModificationException();
    }
}

其他的就不多说了,看倒数第5行,问题就解决了,删除元素之后,手动的让expectedModCount = modCount

那么源代码修改为

List<String> list = new ArrayList<>();
list.add("11");
list.add("12");
list.add("13");
list.add("14");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    
    
    String value = iterator.next();
    if ("12".equals(value)) {
    
    
        iterator.remove();
    }
}
System.out.println(list);

结语

1、集合有很多种遍历方式,在使用跌代器遍历时,删除元素不能使用list提供的remove,iterator.next()首先会检查expectedModCount与modCount是否相等,不相等会抛出并发修改异常。

2、使用Iterator提供的remove方法删除元素,底层会手动的让expectedModCount与modCount相等。

3、forEach、stream.forEach()在遍历集合时底层也会翻译为迭代器,删除元素也会抛出并发修改异常。

4、制作不易,一键三连再走吧,您的支持永远是我最大的动力!

猜你喜欢

转载自blog.csdn.net/admin_2022/article/details/130555576
今日推荐