黑马程序员技术交流社区

标题: 关于集合中并发异常那点事 [打印本页]

作者: lzhuas    时间: 2014-5-27 23:25
标题: 关于集合中并发异常那点事
本帖最后由 lzhuas 于 2014-5-28 23:43 编辑

刚才我复习功课,复习到了多线程的同步,突然想起了视频里毕老师说的List集合中的迭代并发异常的那一部分,我又想到7K里的异步。。。
经过这一连串的疑惑我就想,是不是List是不安全的,迭代里突然两个线程同时操作着同一个集合的数据,就发生了并发异常。为了一究到底,我百度了。以下是解释,希望大家能学习学习:

不要以为只有多线程才有并发访问问题,其实单线程也有。举个例子,对于集合,相信大家经常碰到下面这种异常:
  • java.util.ConcurrentModificationException
  •     at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:449)
  •     at java.util.AbstractList$Itr.next(AbstractList.java:420)

这个异常是由于并发修改集合元素引起的,大家第一个反应多半是多线程问题,结果可能怎么也找不出问题。这里我就模拟一下单线程引发这个并发问题的例子。
  •         ArrayList<String> list=new ArrayList<String>();
  •         list.add("1");
  •         list.add("2");
  •         list.add("3");
  •         list.add("4");
  •         list.add("6");
  •         for(String m:list){
  •            System.out.println(m);
  •             list.add("7");//look here ,problem point
  •         }

上面这个例子只要一执行就会出现异常。为什么呢?

Iterator模式是用于遍历集合类的标准访问方法,我们来看看集合AbstracyList如何创建Iterator。首先AbstractList定义了一个内部类(inner class):
  • private class Itr implements Iterator {
  •         ...
  •     }


而iterator()方法的定义是:
  • public Iterator iterator() {
  •         return new Itr();
  •     }

因此客户端不知道它通过Iterator it = a.iterator();所获得的Iterator的真正类型。
   现在我们关心的是这个申明为private的Itr类是如何实现遍历AbstractList的:
  • private class Itr implements Iterator {
  •         int cursor = 0;
  •         int lastRet = -1;
  •         int expectedModCount = modCount;

Itr类依靠3个int变量(还有一个隐含的AbstractList的引用)来实现遍历,cursor是下一次next()调用时元素的位置,第一次调用next()将返回索引为0的元素。lastRet记录上一次游标所在位置,因此它总是比cursor少1。
变量cursor和集合的元素个数决定hasNext():
  •    public boolean hasNext() {
  •         return cursor != size();
  •     }

方法next()返回的是索引为cursor的元素,然后修改cursor和lastRet的值:
  •     public Object next() {
  •         checkForComodification();
  •         try {
  •             Object next = get(cursor); //注意这里:得到下一个元素
  •             lastRet = cursor++;
  •             return next;
  •         } catch(IndexOutOfBoundsException e) {
  •             checkForComodification();
  •             throw new NoSuchElementException();
  •         }
  •     }

expectedModCount表示期待的modCount值,用来判断在遍历过程中集合是否被修改过。AbstractList包含一个 modCount变量,它的初始值是0,当集合每被修改一次时(调用add,remove等方法),modCount加1。因此,modCount如果不 变,表示集合内容未被修改。

  •     public E get(int index) {
  •         rangeCheck(index);  //检查范围
  •         checkForComodification();//注意这里:检查是否有被修改
  •         return l.get(index+offset);
  •     }


Itr初始化时用expectedModCount记录集合的modCount变量,此后在必要的地方它会检测modCount的值:
  • final void checkForComodification() {
  •         if (modCount != expectedModCount)
  •             throw new ConcurrentModificationException();
  •     }

如果modCount与一开始记录在expectedModeCount中的值不等,说明集合内容被修改过,此时会抛出ConcurrentModificationException。


大家看得很费劲吧?我最后解释一下吧,也就是迭代里有一个安全机制,它会检查进来时和出去时的记录值,就是看修改了没有。要是修改了就发生并发异常,这就是根本原因。如果您有什么别的见解请大声说出来也好让我纠正,谢谢!


作者: 楚轩    时间: 2014-5-27 23:27
赞一个  ,  先看看,留着慢慢消化,
作者: Running    时间: 2014-6-9 22:18
赞一个,异常这个东西我就不理解是什么意思。。。




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2