并发修改异常 (ConcurrntModificationException)原理及其解决方案
问题演示
小伙伴们可以运行一下
遍历集合元素的过程中删除元素
public void testConcurrentModifyException() throws InterruptedException {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(1);
for(Integer num : list){
list.remove(0);
}
}
第二种方式
1.创建10个线程分别共享变量ArrayList添加元素,
2.主线程对ArrayList进行遍历
运行过程中就会抛出ConcurrentModificationException
public void testConcurrentModifyException() throws InterruptedException {
List<Integer> list = new ArrayList<>();
for(int i = 0;i < 10;i++){
new Thread(()-> {
for(int j = 0;j < 100;j++) {
list.add(j);
}
},"A + i").start();
}
for(Integer tmp:list){}
Thread.sleep(20000);
System.out.println(list.size());
}
其实这两种方式触发ConcurrentModificationException的通过相同的方式,看似一个单线程,一个多线程
我在这个地方简述一下这个异常出发的大致原理;
1.首先ArrayList类中有一个共享变量modCount,我们每操作一次集合这个变量都会进行加一操作(增,删,改,不包含查询)
2. 我们在开始遍历集合元素的时候,会记录此时的modCount作为expectedModCount,每次遍历一个元素的的时候都会检查,这两个参数是否相等;如果不想等就会抛出ConcurrentModifyException
第一种方式解决方式是比较简单的
下面是代码示例:
public void testSolveModificationException () {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(1);
Iterator<Integer> it = list.iterator();
while(it.hasNext()) {
Integer num = it.next();
System.out.println(num);
it.remove();
}
System.out.println("size = " + list.size());
}
为什么这个方法没有抛出并发修改异常?大概浏览一下代码,迭代器删除元素是,调用也是ArrrayList对象的remove, 此时成员变量modCount进行++操作,此时迭代器用modCount对给expectModCount进行赋值,所以通过这个方式不会抛出并发修改异常
第二种情况的解决方式:
我们可以分析思路:
出现并发修改异常,多个线程对共享变量ArrayList并发进行读写导致的,一般需要用到锁相关的知识了,我们是无法直接对ArrayList进行加锁的
解决方式
1:使用线程安全的集合(Vector),
这是从jdk 1.1开始就存在的集合,其实也会抛并发修改异常,跟进了一下源码,所有的方法都加了synchronized,为什么还会出现异常呢?这是因为 遍历元素时,各个元素都是独立的释放获取锁,这也造成 增加,遍历元素,交替的获取锁的所以此时调用checkForComodification也会抛ConcurrentModificationException;
2.使用jdk提供的提供的工具类,
但是下面这种方式,并不能解决并发并发修改异常,小伙伴可以多实验几次,就会复现;其实jdk工具类的实现的基本逻辑是基于装饰的设计模式;装饰之后是实现类SynchronizedRandomAccessList;其实使用这种方式,只是增,删,改,编程进行了加锁,对于遍历是没有加锁的 源码了只有 Must be manually synched by user 大概意思是遍历需要用户自己实现同步;
虽然是一个大坑,但是我们开发过程中很少使用,也就很少遇到了;
List list = Collections.synchronizedList(new ArrayList<>())
public void testConcurrentModifyExceptionV2() throws InterruptedException {
//List<Integer> list = new ArrayList<>();
List<Integer> list = Collections.synchronizedList(new ArrayList<>());
for(int i = 0;i < 10;i++){
new Thread(()-> {
for(int j = 0;j < 100;j++) {
list.add(j);
}
},"A + i").start();
}
int cnt = 0;
for(Integer tmp:list){
cnt++;
}
System.out.println(cnt);
Thread.sleep(20000);
System.out.println(list.size());
}