当前位置: 代码迷 >> 综合 >> 错误笔记-ConcurrentModificationException
  详细解决方案

错误笔记-ConcurrentModificationException

热度:93   发布时间:2024-02-12 07:37:44.0

错误笔记-ConcurrentModificationException

            • 1)故障现象
            • 2)导致原因
            • 3)解决方案
            • 4)优化建议

1)故障现象

在使用多线程操作ArraryList,HashSet,HashMap等线程不安全集合的时候,会出现如下报错:

java.util.ConcurrentModificationException

这个报错信息的意思为并发修改异常
代码如下

public class NotSafe {public static void main(String[] args) {List<String> list = new ArrayList<>();for (int i = 0; i < 30; i++) {new Thread(()->{list.add(UUID.randomUUID().toString().substring(0,4));System.out.println(list);},"A").start();}}
}
2)导致原因
1)没加锁
2)迭代的时候数据变化,导致希望的集合个数,和真实的集合个数不一致,所以会导致报错

简单的说就是两个线程在对资源操作的时候进行了争抢,导致A线程在读取集合数据输出的时候,B线程又往集合中新增了数据,导致了A线程读取的集合个数和真实个数不一样,导致报错。
可以查看这一段源码,这段源码作用是,指针遍历集合

@SuppressWarnings("unchecked")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()
这个方法是用来判断modCount这个值是否和所期望的modCount相同,如果不相同就会抛出ConcurrentModificationException这个异常
modCount是什么根据浏览其他资料,这个数值其实是修改次数,所以源码是通过判断这个集合的修改次数来判断这个集合是否在读取的时候被其他线程修改

final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();
}
3)解决方案

有三种解决方案
1)使用线程安全的集合类,如Vector

List<String> list = new Vector<>();

2)使用集合工具类来给ArraryList穿上层带锁的衣服

List<String> list = Collections.synchronizedList(new ArrayList<>());
Set<String> set = Collections.synchronizedSet(new HashSet<>());
Map<String,Object> map = Collections.synchronizedMap(new HashMap<>());

3)使用juc提供的实现类

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;List<String> list = new CopyOnWriteArrayList<>();
Set<String> set = new CopyOnWriteArraySet<>();
Map<String,Object> map = new ConcurrentHashMap<>();

这三个实现类,核心是写时复制技术
用人话说就是多线程读的时候是同一个集合,如果其中一个线程需要修改集合数据,就需要在集合复制出来的新集合里面修改,然后在将旧集合覆盖,然其他集合来读取新的集合,从而实现线程的安全,简单的说就是读写分离

4)优化建议

以上三种优化方案,建议使用juc提供的实现类,这样可以优化集合操作的性能,不会因为修改导致读取集合操作的性能下降