Java ConcurrentModificationException异常原因和解决方法

在前面一篇文章中提到,对Vector、ArrayList在迭代的时候如果同时对其进行修改就会抛出java.util.ConcurrentModificationException异常。下面我们就来讨论以下这个异常出现的原因以及解决办法。

  以下是本文目录大纲:

  一.ConcurrentModificationException异常出现的原因

  二.在单线程环境下的解决办法

  三.在多线程环境下的解决方法

  若有不正之处请多多谅解,并欢迎批评指正

  请尊重作者劳动成果,转载请标明原文链接:

  http://www.cnblogs.com/dolphin0520/p/3933551.html

一.ConcurrentModificationException异常出现的原因

  先看下面这段代码:


1

2

3

4

5

6

7

8

9

10

11

12

public class Test {

    public static void main(String[] args)  {

        ArrayList<Integer> list = new ArrayList<Integer>();

        list.add(2);

        Iterator<Integer> iterator = list.iterator();

        while(iterator.hasNext()){

            Integer integer = iterator.next();

            if(integer==2)

                list.remove(integer);

        }

    }

}

   运行结果:

  

  从异常信息可以发现,异常出现在checkForComodification()方法中。

  我们不忙看checkForComodification()方法的具体实现,我们先根据程序的代码一步一步看ArrayList源码的实现:

  首先看ArrayList的iterator()方法的具体实现,查看源码发现在ArrayList的源码中并没有iterator()这个方法,那么很显然这个方法应该是其父类或者实现的接口中的方法,我们在其父类AbstractList中找到了iterator()方法的具体实现,下面是其实现代码:


1

2

3

public Iterator<E> iterator() {

    return new Itr();

}

   从这段代码可以看出返回的是一个指向Itr类型对象的引用,我们接着看Itr的具体实现,在AbstractList类中找到了Itr类的具体实现,它是AbstractList的一个成员内部类,下面这段代码是Itr类的所有实现:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

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;

        catch (IndexOutOfBoundsException e) {

        throw new ConcurrentModificationException();

        }

    }

 

    final void checkForComodification() {

        if (modCount != expectedModCount)

        throw new ConcurrentModificationException();

    }

}

   首先我们看一下它的几个成员变量:

  cursor:表示下一个要访问的元素的索引,从next()方法的具体实现就可看出

  lastRet:表示上一个访问的元素的索引

  expectedModCount:表示对ArrayList修改次数的期望值,它的初始值为modCount。

  modCount是AbstractList类中的一个成员变量


1

protected transient int modCount = 0;

   该值表示对List的修改次数,查看ArrayList的add()和remove()方法就可以发现,每次调用add()方法或者remove()方法就会对modCount进行加1操作。

  好了,到这里我们再看看上面的程序:

  当调用list.iterator()返回一个Iterator之后,通过Iterator的hashNext()方法判断是否还有元素未被访问,我们看一下hasNext()方法,hashNext()方法的实现很简单:


1

2

3

public boolean hasNext() {

    return cursor != size();

}

   如果下一个访问的元素下标不等于ArrayList的大小,就表示有元素需要访问,这个很容易理解,如果下一个访问元素的下标等于ArrayList的大小,则肯定到达末尾了。

  然后通过Iterator的next()方法获取到下标为0的元素,我们看一下next()方法的具体实现:


1

2

3

4

5

6

7

8

9

10

11

public E next() {

    checkForComodification();

 try {

    E next = get(cursor);

    lastRet = cursor++;

    return next;

 catch (IndexOutOfBoundsException e) {

    checkForComodification();

    throw new NoSuchElementException();

 }

}

   这里是非常关键的地方:首先在next()方法中会调用checkForComodification()方法,然后根据cursor的值获取到元素,接着将cursor的值赋给lastRet,并对cursor的值进行加1操作。初始时,cursor为0,lastRet为-1,那么调用一次之后,cursor的值为1,lastRet的值为0。注意此时,modCount为0,expectedModCount也为0。

  接着往下看,程序中判断当前元素的值是否为2,若为2,则调用list.remove()方法来删除该元素。

  我们看一下在ArrayList中的remove()方法做了什么:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

public boolean remove(Object o) {

    if (o == null) {

        for (int index = 0; index < size; index++)

            if (elementData[index] == null) {

                fastRemove(index);

                return true;

            }

    else {

        for (int index = 0; index < size; index++)

            if (o.equals(elementData[index])) {

                fastRemove(index);

                return true;

            }

    }

    return false;

}

 

 

private void fastRemove(int index) {

    modCount++;

    int numMoved = size - index - 1;

    if (numMoved > 0)

        System.arraycopy(elementData, index+1, elementData, index,

                numMoved);

    elementData[--size] = null// Let gc do its work

}

   通过remove方法删除元素最终是调用的fastRemove()方法,在fastRemove()方法中,首先对modCount进行加1操作(因为对集合修改了一次),然后接下来就是删除元素的操作,最后将size进行减1操作,并将引用置为null以方便垃圾收集器进行回收工作。

  那么注意此时各个变量的值:对于iterator,其expectedModCount为0,cursor的值为1,lastRet的值为0。

  对于list,其modCount为1,size为0。

  接着看程序代码,执行完删除操作后,继续while循环,调用hasNext方法()判断,由于此时cursor为1,而size为0,那么返回true,所以继续执行while循环,然后继续调用iterator的next()方法:

  注意,此时要注意next()方法中的第一句:checkForComodification()。

  在checkForComodification方法中进行的操作是:


1

2

3

4

final void checkForComodification() {

    if (modCount != expectedModCount)

    throw new ConcurrentModificationException();

}

   如果modCount不等于expectedModCount,则抛出ConcurrentModificationException异常。

  很显然,此时modCount为1,而expectedModCount为0,因此程序就抛出了ConcurrentModificationException异常。

  到这里,想必大家应该明白为何上述代码会抛出ConcurrentModificationException异常了。

  关键点就在于:调用list.remove()方法导致modCount和expectedModCount的值不一致。

  注意,像使用for-each进行迭代实际上也会出现这种问题。

二.在单线程环境下的解决办法

  既然知道原因了,那么如何解决呢?

  其实很简单,细心的朋友可能发现在Itr类中也给出了一个remove()方法:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

public void remove() {

    if (lastRet == -1)

    throw new IllegalStateException();

       checkForComodification();

 

    try {

    AbstractList.this.remove(lastRet);

    if (lastRet < cursor)

        cursor--;

    lastRet = -1;

    expectedModCount = modCount;

    catch (IndexOutOfBoundsException e) {

    throw new ConcurrentModificationException();

    }

}

   在这个方法中,删除元素实际上调用的就是list.remove()方法,但是它多了一个操作:


1

expectedModCount = modCount;

   因此,在迭代器中如果要删除元素的话,需要调用Itr类的remove方法。

  将上述代码改为下面这样就不会报错了:


1

2

3

4

5

6

7

8

9

10

11

12

public class Test {

    public static void main(String[] args)  {

        ArrayList<Integer> list = new ArrayList<Integer>();

        list.add(2);

        Iterator<Integer> iterator = list.iterator();

        while(iterator.hasNext()){

            Integer integer = iterator.next();

            if(integer==2)

                iterator.remove();   //注意这个地方

        }

    }

}

三.在多线程环境下的解决方法

  上面的解决办法在单线程环境下适用,但是在多线程下适用吗?看下面一个例子:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

public class Test {

    static ArrayList<Integer> list = new ArrayList<Integer>();

    public static void main(String[] args)  {

        list.add(1);

        list.add(2);

        list.add(3);

        list.add(4);

        list.add(5);

        Thread thread1 = new Thread(){

            public void run() {

                Iterator<Integer> iterator = list.iterator();

                while(iterator.hasNext()){

                    Integer integer = iterator.next();

                    System.out.println(integer);

                    try {

                        Thread.sleep(100);

                    catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                }

            };

        };

        Thread thread2 = new Thread(){

            public void run() {

                Iterator<Integer> iterator = list.iterator();

                while(iterator.hasNext()){

                    Integer integer = iterator.next();

                    if(integer==2)

                        iterator.remove(); 

                }

            };

        };

        thread1.start();

        thread2.start();

    }

}

   运行结果:

  

  有可能有朋友说ArrayList是非线程安全的容器,换成Vector就没问题了,实际上换成Vector还是会出现这种错误。

  原因在于,虽然Vector的方法采用了synchronized进行了同步,但是由于Vector是继承的AbstarctList,因此通过Iterator来访问容器的话,事实上是不需要获取锁就可以访问。那么显然,由于使用iterator对容器进行访问不需要获取锁,在多线程中就会造成当一个线程删除了元素,由于modCount是AbstarctList的成员变量,因此可能会导致在其他线程中modCount和expectedModCount值不等。

  就比如上面的代码中,很显然iterator是线程私有的,

  初始时,线程1和线程2中的modCount、expectedModCount都为0,

  当线程2通过iterator.remove()删除元素时,会修改modCount值为1,并且会修改线程2中的expectedModCount的值为1,

  而此时线程1中的expectedModCount值为0,虽然modCount不是volatile变量,不保证线程1一定看得到线程2修改后的modCount的值,但是也有可能看得到线程2对modCount的修改,这样就有可能导致线程1中比较expectedModCount和modCount不等,而抛出异常。

  因此一般有2种解决办法:

  1)在使用iterator迭代的时候使用synchronized或者Lock进行同步;

  2)使用并发容器CopyOnWriteArrayList代替ArrayList和Vector。

  关于并发容器的内容将在下一篇文章中讲述。

  参考资料:

  http://blog.csdn.net/izard999/article/details/6708738

  http://www.2cto.com/kf/201403/286536.html


 文末加上原文链接:[http://wely.iteye.com/blog/2324814]

时间: 2024-08-03 06:20:16

Java ConcurrentModificationException异常原因和解决方法的相关文章

收集的比较全的automation服务器不能创建对象 异常原因和解决方法第1/2页_javascript技巧

1.automation服务器不能创建对象 只需要运行:regsvr32 scrrun.dll 就可以了 2.未将对象引用设置到对象的实例. 1.ViewState 对象为Unll. 2.DateSet 空. 3.sql语句或Datebase的原因导致DataReader空. 4.声明字符串变量时未赋空值就应用变量. 5.未用new初始化对象. 6.Session对象为空. 7.对控件赋文本值时,值不存在 8.使用Request.QueryString()时,所获取的对象不存在,或在值为空时未赋

tomcat6下jsp出现getOutputStream() has already been called for this response异常的原因和解决方法_JSP编程

1.在tomcat6.0下jsp出现getOutputStream() has already been called for this response异常的原因和解决方法 在tomcat6.0下jsp中出现此错误一般都是在jsp中使用了输出流(如输出图片验证码,文件下载等),没有妥善处理好的原因. 具体的原因就是: 在tomcat中jsp编译成servlet之后在函数_jspService(HttpServletRequest request, HttpServletResponse res

Validation of viewstate MAC failed异常的原因及解决方法

Validation of viewstate MAC failed异常的原因及解决方法 事件日志里偶尔能发现这个错误,却一直找不到有效的解决方法.经过仔细研究分析之后,发现出现这个错误的概率还是很小的,还需要满足多个条件才行: 1.页面中使用了GridView, DetailsViews, FormView等采用内置数据绑定控件 2.就是这个页面的内容较多,在网速较慢的情况下需要较长的时间才能加载完 3.在页面还没有加载完成的情况下,点击了Postback性质的按钮.如果是get方式的url链

关键词排名下降原因及解决方法

近段时间听见很多朋友说自己网站的关键词掉得很厉害,彭宇诚维护的一个网站也遭遇了这样的状况,几个重要的关键词排名在百度从第2被降到第11.下面彭宇诚结合自己的一些经验谈谈关键词排名下降原因及我们应该采取的应对策略. 浅析关键词排名下降原因及解决方法: 1.网站服务器不稳定或更换服务器 这种情况的出现最最难让人接受,服务器的不稳定会照成网站打开速度很慢甚至无法打开.这样当搜索引擎蜘蛛去访问网站正是打不开的时候,搜索引擎(尤其是百度)肯定会对网站进行一些处罚,轻则网站被降权,重则网站被K. 更换服务器

网站快照回档的常见原因和解决方法分析

众所周知,网站快照是判断网站权重的方式之一,也是分析我们网站被百度重视程度的一个关键因素之一,但是笔者发现很多朋友包括笔者的网站经常会出现快照回档的问题,快照回档说明了那些问题?我们又应该从那些方面提高网站权重保证我们网站快照的天天更新呢?下面笔者就自己在网站运营优化过程中关于快照回档的几点原因和解决方法技巧和大家一起分享一下.好了,闲话短续咱们进入今天的主题.我将快照回档的原因大致总结了一下几点,然后一一进行分析. 第一,服务器因素.这个是快照回档的一个非技术性因素,但是对于网站快照的影响又是

数据库-select @@identity as id的返回值偶尔出现0原因及解决方法

问题描述 select @@identity as id的返回值偶尔出现0原因及解决方法 3C 在执行插入语句后,数据插入到数据库中,然后用同样的dbconn执行select @@identity as id语句,有些时候返回了正常的自增id,可是有些时候返回了0,请问一下有大神知道原因及解决方法吗??? 解决方案 补充一下,数据库是mysql,执行语句用的是java的jdbc,麻烦各位大神了 解决方案二: QT出现"undefined reference to `vtable for'&quo

Android Listview 滑动过程中提示图片重复错乱的原因及解决方法_Android

主要分析Android中Listview滚动过程造成的图片显示重复.错乱.闪烁的原因及解决方法,顺便跟进Listview的缓存机制. 1.原因分析 Listview item 缓存机制:为了使得性能更优,Listview会缓存行item(某行对应的view).listview通过adapter的getview函数获得每行的item.滑动过程中, a.如果某行item已经划出屏幕,若该item不在缓存内,则put进缓存,否则更新缓存: b.获取滑入屏幕的行item之前会先判断缓存中是否有可用的it

键词排名下降原因及解决方法

今天分享的是"如何添加新关键词,不影响原有关键词的排名",如果有朋友要了解关于关键词为什么排名变化,该如何解决等类似问题,请查阅 <彭宇诚:关键词排名变化原因及解决方法> . 浅析关键词排名下降原因及解决方法: 1.网站服务器不稳定或更换服务器 这种情况的出现最最难让人接受,服务器的不稳定会照成网站打开速度很慢甚至无法打开.这样当搜索引擎蜘蛛去访问网站正是打不开的时候,搜索引擎(尤其是百度)肯定会对网站进行一些处罚,轻则网站被降权,重则网站被K. 更换服务器就像一个人重新搬

Win7打开word提示word以安全模式启动的原因及解决方法教程

word是一款的办公软件,主要用于编辑和处理文档里面的内部数据,有些Win7系统用户每次打开word时都会弹出提示"word以安全模式启动",出现这种情况该如何解决呢?下面小编为大家分享Win7打开word提示"word以安全模式启动"的解决方法.一起去看看吧! 故障原因:会出现word以安全模式启动的提示是因为word的Normal.dot模板损坏,这是用于word自动保存的模板,导致他损坏的原因可能是编辑过程中电脑突然断电或软件异常关闭. 解决方法: 1.只要删