单线程也可能引发"并发"访问异常

  不要以为只有多线程才有并发访问问题,其实单线程也有。举个例子,对于集合,相信大家经常碰到下面这种异常:

  1. java.util.ConcurrentModificationException
  2.     at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:449)
  3.     at java.util.AbstractList$Itr.next(AbstractList.java:420)

这个异常是由于并发修改集合元素引起的,大家第一个反应多半是多线程问题,结果可能怎么也找不出问题。这里我就模拟一下单线程引发这个并发问题的例子。

  1.         ArrayList<String> list=new ArrayList<String>();
  2.         list.add("1");
  3.         list.add("2");
  4.         list.add("3");
  5.         list.add("4");
  6.         list.add("6");
  7.         for(String m:list){
  8.            System.out.println(m);
  9.             list.add("7");//look here ,problem point
  10.         }

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

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

  1.  private class Itr implements Iterator {
  2.         ...
  3.     }

 

而iterator()方法的定义是:

  1.  public Iterator iterator() {
  2.         return new Itr();
  3.     }

因此客户端不知道它通过Iterator it = a.iterator();所获得的Iterator的真正类型。

   现在我们关心的是这个申明为private的Itr类是如何实现遍历AbstractList的:

  1. private class Itr implements Iterator {
  2.         int cursor = 0;
  3.         int lastRet = -1;
  4.         int expectedModCount = modCount;

 Itr类依靠3个int变量(还有一个隐含的AbstractList的引用)来实现遍历,cursor是下一次next()调用时元素的位置,第一次调用next()将返回索引为0的元素。lastRet记录上一次游标所在位置,因此它总是比cursor少1。

变量cursor和集合的元素个数决定hasNext():

  1.    public boolean hasNext() {
  2.         return cursor != size();
  3.     }

方法next()返回的是索引为cursor的元素,然后修改cursor和lastRet的值:

  1.     public Object next() {
  2.         checkForComodification();
  3.         try {
  4.             Object next = get(cursor); //注意这里:得到下一个元素
  5.             lastRet = cursor++;
  6.             return next;
  7.         } catch(IndexOutOfBoundsException e) {
  8.             checkForComodification();
  9.             throw new NoSuchElementException();
  10.         }
  11.     }

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

 

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

 

Itr初始化时用expectedModCount记录集合的modCount变量,此后在必要的地方它会检测modCount的值:

  1. final void checkForComodification() {
  2.         if (modCount != expectedModCount)
  3.             throw new ConcurrentModificationException();
  4.     }

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


posted @ 2009-01-06 15:40  lovingprince  阅读(2208)  评论(0编辑  收藏  举报