ConcurrentModificationException 的根源分析

    我们可以查看JDKAPI来参考该异常的解释 : 当方法检测到对象的并发修改时,抛出此异常。

 

我门系统中大部分在是在使用Collection上进行迭代时抛出异常,但是我们系统都是使用的单线程,为什么会发生该异常了

我们参考下面错误代码,一个简单的需求,用户获取一个集合数据,需要讲不满足用户需求的数据删除掉

 

代码:

//从业务方法中查询数据

           List<String> list = service.queryResult();

           for (String value : list)

           {

                 //bbb数据不是用户需要的数据,我们需要移除

                 if (value.equals("aaa"))

                 {

                       //将不满足用户数据删除

                      list.remove(value);

                 }

           }

  运行结果:

       Exception in thread"main" java.util.ConcurrentModificationException

                atjava.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)

                at java.util.AbstractList$Itr.next(AbstractList.java:343)

               at com.demo.ConcurrentExceptionDemo.main(ConcurrentExceptionDemo.java:24)

我们可以发现上面的代码并没有什么并发问题,到底是什么原因发生该异常,我们可以再次查看API文档

  引用文档上面的话:“此异常不会始终指出对象已经由不同线程并发修改。如果单线程发出违反对象协定的方法调用序列,则该对象可能抛出此异常”现在我们知道了单线程也是会抛出该异常的,由于我们违反了对象的协定的方法调用序列,但是我们是在哪里违反了协定了?我们再次查看API文档上面发现: “行该操作的迭代器称为快速失败 迭代器,因为迭代器很快就完全失败,而不会冒着在将来某个时间任意发生不确定行为的风险”,这句话什么意思了?看了很久没有搞明白,所以直接查看源代码发现:、

 

finalvoid checkForComodification(){

          if (modCount != expectedModCount)

           thrownew ConcurrentModificationException();

      }

}

我们使用的迭代器在调用next()调用了改方法checkForComodification(),当每次检查modCount != expectedModCount就会抛出该异常,

下面我们来再次分析我们的代码

modCount是集合每次在调用removeadd方法时都会记录修改次数modCount++但是我们迭代产生后expectedModCount就是一个固定值了,所以你只要在迭代时修改了集合就会造成modCount != expectedModCount,所以就抛出了ConcurrentModificationException

 

现在我们应该已经搞清楚了改异常产生的原因了,但是我们还是要实现上面的需求,不满足要求还是要删除,我们代码该怎么实现了

方案一:通过调用迭代器的remove方法来实现删除

Iterator<String> ite = list.iterator();

           while(ite.hasNext())

           {

                 //aaa数据不是用户需要的数据,我们需要移除

                 if (ite.next().equals("aaa"))

                 {

                      list.remove("aaa");

                 }

           }

我们来看看为什么调用迭代器的remove方法不会发生该异常了,下面是该迭代器的remove方法源码

publicvoid remove() {

          if (lastRet == -1)

           thrownew IllegalStateException();

           checkForComodification();

          try {

           AbstractList.this.remove(lastRet);

           if (lastRet < cursor)

               cursor--;

           lastRet = -1;

           expectedModCount = modCount;

          } catch (IndexOutOfBoundsException e) {

           thrownew ConcurrentModificationException();

          }

      }

我们发现上面加粗标红的字体就应该明白原因了吧。

方案二:首先获取需要删除的元素,然后通过迭代要删除的元素来删除

           List<String> delResults= new ArrayList<String>();

           for (String value : list)

           {

                 //bbb数据不是用户需要的数据,我们需要移除

                 if (value.equals("bbb"))

                 {

                      delResults.add(value);

                 }

           }

           //然后迭代要删除的集合来删除

           for (String delValue :delResults)

           {

                 list.remove(delValue);

           }

这样也是没有问题的,但是我们发现方案二好像比方法一多做了线性循环。

 

 

集合抛出ConcurrentModificationException进阶

我们下面来看一下下面代码

package com.demo;

import java.util.HashSet;

import java.util.Random;

import java.util.Set;

import java.util.concurrent.SynchronousQueue;

public class HiddenIterator

{

    privateSet<Integer> set = new HashSet<Integer>();

    public voidadd(Integer i)

    {

        set.add(i);

    }

    public voidremove(Integer i)

    {

        set.remove(i);

    }

    public voidaddTenThings()

    {

        Random rand = new Random();

        for (int i=0;i<10;i++)

        {

            add(rand.nextInt());

            System.out.println("DEBUGadd ten elements to set : " + set);

        }

    }

    public staticvoid main(String []args)

    {

        finalHiddenIterator hi = new HiddenIterator();

        newThread(){

            publicvoid run() {

                hi.addTenThings();

            };

        }.start();

        hi.addTenThings();

    }

}

 

我们来看看改代码,暂时不要管上面标红的行,就是在添加了一个元素打印集合中的值,我们看看程序的原意就是通过两个线程来向集合中添加元素,运行该代码发现下面结果

Exception in thread "Thread-0" java.util.ConcurrentModificationException

      atjava.util.HashMap$HashIterator.nextEntry(HashMap.java:793)

      at java.util.HashMap$KeyIterator.next(HashMap.java:828)

      atjava.util.AbstractCollection.toString(AbstractCollection.java:421)

      at java.lang.String.valueOf(String.java:2826)

      atjava.lang.StringBuilder.append(StringBuilder.java:115)

      atcom.demo.HiddenIterator.addTenThings(HiddenIterator.java:28)

      at com.demo.HiddenIterator$1.run(HiddenIterator.java:37)

 

我们上面已经知道集合是在做迭代集合的时候才会发生该异常,我们可以看集合的add()方法里也完全没有抛出该异常的代码。这就百思不得其解了,然后一句一句的代码查看,原来发现问题原因处在了我们上面标红的代码行。我们其实只要注释掉打印语句就不会发生该异常了

原来我们打印集合时,会自动调用toString()方法,我们现在查看toString()方法源码

public String toString() {

        Iterator<E> i =iterator();

      if (! i.hasNext())

          return"[]";

 

      StringBuilder sb = new StringBuilder();

      sb.append('[');

      for (;;) {

          E e = i.next();

          sb.append(e == this ? "(this Collection)" : e);

          if (! i.hasNext())

           return sb.append(']').toString();

          sb.append(", ");

      }

原来在调用集合 toString 方法时做了集合的迭代操作,所以导致了改异常的产生,因此我们在使用集合时要注意集合的一些隐式迭代操作,整理了一下集合的 hashCode,equals,toString,containsAll,removeAll,retainAll 方法都做了隐式迭代,所以我们在并发环境下将集合作为另一个容器的元素和一个容易的 key 时要注意,以及把容器作为参数的构造函数时都会对容器进行迭代,因此在这些操作下都可能会引起 ConcurrentModificationException

 

 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值