深入分析集合并发修改异常(源码分析)java.util.ConcurrentModificationException

来源:互联网 发布:erp软件购买ufsky 编辑:程序博客网 时间:2024/06/05 06:38
          在我们迭代操作集合时,有时可能需要改变集合的元素,若操作不当,经常都会出现不正确的结果或者并发操作异常,如下:
Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.AbstractList$Itr.checkForComodification(Unknown Source)at java.util.AbstractList$Itr.next(Unknown Source)at com.qiuyang.Test.Test.main(Test.java:14)

在学习JAVA的时候,大家都知道在迭代集合时,不能去修改集合,但是WHY? 在查看API集合部分源码时,终于找到了原因,先来个例子:

import java.util.*;public class Test {public static void main(String[] args){ArrayList<String> list=new ArrayList<String>();list.add("张三");list.add("李四");list.add("王五");list.add("赵六");for( Iterator<String> it=list.iterator();it.hasNext();){String name=it.next();if("李四".equals(name)){list.remove(name);//集合操作,改变了集合的版本,但不会改变迭代器的版本}elseSystem.out.print(name+" ");}System.out.println("\n"+list);}}
执行结果:

张三 Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.AbstractList$Itr.checkForComodification(Unknown Source)at java.util.AbstractList$Itr.next(Unknown Source)at com.qiuyang.Test.Test.main(Test.java:14)
可以看到,程序打印了张三,然后出现并发修改异常,那这个异常时怎么产生的呢?我们找到AbstractList的源代码,取出其中部分,如下:

在AbstractList类中内部成员有:protected transient int modCount = 0;注:该modCount相当于集合的版本号,集合内容每改变一次,其值就+1。观察下面内部类可以知道,在next()、remove()方法都会去判断集合版本号与迭代器版本号是否一致checkForComodification();内部方法有:public Iterator<E> iterator() {return new Itr();    }还有一个内部类:private class Itr implements Iterator<E> {int cursor = 0;int lastRet = -1;//***创建迭代器时就讲版本号给了迭代器int expectedModCount = modCount;public boolean hasNext() {            return cursor != size();}public E next() {            checkForComodification();    try {E next = get(cursor);lastRet = cursor++;return next;    } catch (IndexOutOfBoundsException e) {checkForComodification();throw new NoSuchElementException();    }}public void remove() {    if (lastRet == -1)throw new IllegalStateException();            checkForComodification();    try {AbstractList.this.remove(lastRet);if (lastRet < cursor)    cursor--;lastRet = -1;expectedModCount = modCount;//迭代器进行删除时,会改变it的版本    } catch (IndexOutOfBoundsException e) {throw new ConcurrentModificationException();    }}//检查迭代器的版本与集合的版本是否一致,不同则抛出异常final void checkForComodification() {    if (modCount != expectedModCount)throw new ConcurrentModificationException();} }

可以看出,迭代器的next()方法,每次都会检查版本号是否一致,modCount为集合的版本号,excepectedModCount为迭代器的版本号,当版本号不一致时,则出现并发修改异常。下面具体分析一下执行过程:

该案列中,集合list.size()=4,遍历集合时创建集合迭代器,此时有 expectedModCount = modCount=4,迭代器的cursor=0,

1cursor=0,it.next()检查版本号一致,取出“张三”,cursor=cursor+1=1,打印,然后it.hasNext()函数判断cursor!=list.size(),返回true

2cursor=1,it.next()检查版本号一致,取出“李四”,cursor=cursor+1=2,进入if语句,移除了“李四”,此时集合内容改变,版本号modCount++,5.然后it.hasNext()判断cursor(2)!=size(4),true

3cursor=2,it.next()检查发现版本号不一致expectedModCount! = modCount,抛出并发修改异常。

若将list.remove(name)改为迭代器的操作it.remove(),则会得到正确的结果如下,这是因为迭代器的remove()方法修改了版本号,如上源代码所示。

张三 王五 赵六 [张三, 王五, 赵六]
二、下面看一个更有意思的例子,同样为集合的并发修改,却没有抛出异常

import java.util.*;public class Test {public static void main(String[] args){ArrayList<String> list=new ArrayList<String>();list.add("张三");list.add("李四");list.add("王五");list.add("赵六");for( Iterator<String> it=list.iterator();it.hasNext();){String name=it.next();if("王五".equals(name)){list.remove(name);//集合操作,不会改变迭代器的版本,会抛出异常}elseSystem.out.print(name+" ");}System.out.println("\n"+list);}}
执行结果为:

张三 李四 [张三, 李四, 赵六]
可以看到,程序虽然没有抛出异常,但却并没有遍历完整个集合,意味着迭代器提前停止了,具体分析如下:

1cursor=0; it.next()检查版本号一致,取出“张三”,cursor++1,打印“张三”,然后, it.hasNext()判断cursor(1)!=size(4),true;

2cursor=1,it.next()检查版本号一致,取出“李四”,cursor++等于2,打印李四,然后,it.hasNext()判断cursor(2)!=size(4);

3cursor=2,it.next()检查版本号一致,取出“王五”,cursor++等于3;进入if语句,list.remove(“王五”),此时,版本号modCount++,且集合大小改变list.size()=3,然后,it.hasNext()判断发现cursor(3)==size(3),返回false,迭代结束,故得到结果:张三李四

(也是因为it.hasNext()返回false退出循环,所以才没有再进入it.next()函数,故没有产生并发修改异常

若将list.remove(name)语句改为 it.remove(),即用迭代器的操作去删除集合的元素,则会得到正确的结果,如下:

张三 李四 赵六 [张三, 李四, 赵六]

 总结:在对集合进行迭代时,不要去操作集合(指的是用集合去增删元素),但可以用迭代器去操作集合,it.remove(),对于List集合,还可以用ListIterator进行add()操作;

  造成上述现象原因,集合操作会改变集合版本号modCount而不会改变迭代器的版本号excepectedModCount,而迭代器操作则集合版本号和迭代器的版本号都会同步的改变,故不会产生异常。


2 0