解决java.util.ConcurrentModificationException,深究原理

在项目的时候,报了一个错 java.util.ConcurrentModificationException

发现是list的remove方法报错,来总结一下:

例子:

public void test(){
    List<String> list=new ArrayList<String>();
    list.add("aaa");
    list.add("bbb");
    list.add("ccc");
    list.stream().forEach(e->{
        if ("bbb".equals(e)){
            list.remove(e);
        }
    });
    list.stream().forEach(System.out::println);
}

报错信息:

java.util.ConcurrentModificationException
	at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1388)
	at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
	at cn.ziwei.test.test.test(test.java:22)

点进去,发现是这里报错:

public void forEachRemaining(Consumer<? super E> action) { // action:{“aaa","bbb","ccc"}
    int i, hi, mc; // hoist accesses and checks from loop
    ArrayList<E> lst; Object[] a;
    if (action == null)
        throw new NullPointerException();
    if ((lst = list) != null && (a = lst.elementData) != null) {
        if ((hi = fence) < 0) {
            mc = lst.modCount;  // modCount是修改次数,这里等于3,因为add了3次
            hi = lst.size;
        }
        else
            mc = expectedModCount;
        if ((i = index) >= 0 && (index = hi) <= a.length) {
            for (; i < hi; ++i) {
                @SuppressWarnings("unchecked") E e = (E) a[i];
                action.accept(e);  //这里回去执行你写的逻辑,也就是if里面的判断
            }
            // 上面的for执行完之后,就会把bbb删除,这个时候lst.modCount=4,因为remove了一次(也就是说,如果if里面的操作是add,也是会报同样的错误)
            // 而mc=3,显然if不成立
            if (lst.modCount == mc)
                return;
        }
    }
    throw new ConcurrentModificationException();
}

增强for也是如此,大家可以去看一下。不只是remove会报错,add也会只要能够使modCount发生变化的,都会报java.util.ConcurrentModificationException

下面是增强for,抛异常的地方

例子:

public void test(){
    List<String> list=new ArrayList<String>();
    list.add("aaa");
    list.add("bbb");
    list.add("ccc");
    for(String s:list){
        if ("bbb".equals(s)) {
            list.add("ddd");
        }
    }
    list.stream().forEach(System.out::println);
}
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

解决方法:

​ 想到了迭代器,强大的迭代器!!

public void test(){
    List<String> list=new ArrayList<String>();
    list.add("aaa");
    list.add("bbb");
    list.add("ccc");
    Iterator<String> it = list.iterator();
    while (it.hasNext()){
        String next = it.next();
        if ("bbb".equals(next)){
            it.remove();
        }
    }
    list.stream().forEach(System.out::println);
}

不会报错,正常输出。Why?

因为:我们使用的list.remove()或者add()是使用的arraylist里面的方法。这些方法内部会增加modCount的值,而没有修改mc/expectedModCount的值,比如remove的源码:

public E remove(int index) {
    rangeCheck(index);
    modCount++;
    E oldValue = elementData(index);
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work
    return oldValue;
}

而list的迭代器的remove:

private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;
    
	Itr() {}
    
    public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;   // 改变expectedmodcount来使其不报错
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
}

关键:expectedModCount = modCount; // 改变expectedmodcount来使其不报错

Java中的`ConcurrentModificationException`通常发生在多线程环境下,当一个集合(如ArrayList、LinkedList等)在遍历时被其他线程修改了其内容,这将导致异常抛出,因为集合内部的数据结构已经被破坏,无法继续迭代。要避免这种异常,可以采取以下几种策略: 1. **同步访问**:在遍历集合的同时锁定它,确保在遍历期间不会发生改变。例如,你可以使用`synchronized`关键字或更推荐的`Collections.synchronizedList()`方法来同步列表。 ```java synchronized(list) { for (Element element : list) { // ...处理元素... } } ``` 2. **Copy-on-write模式**:如果集合支持并发访问并允许安全的复制,如`CopyOnWriteArrayList`,可以在遍历时创建一个副本,然后在副本上操作,原集合不会受影响。 ```java CopyOnWriteArrayList<Element> copy = new CopyOnWriteArrayList<>(list); for (Element element : copy) { // ...处理元素... } ``` 3. **观察者模式**:使用`java.util.Observable`和`java.util.Observer`来通知数据变化,而不是直接修改。观察者可以在更新后自行决定是否需要重新遍历。 4. **使用`Iterator`的try-with-resources语句**:`Iterator`有一个`hasNext()`和`next()`方法组合,可以确保在遇到异常时自动关闭迭代器。 ```java Iterator<Element> iterator = list.iterator(); while (iterator.hasNext()) { try { Element element = iterator.next(); // ...处理元素... } catch (ConcurrentModificationException e) { break; // 或者记录错误并退出 } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值