单线程也可能引发"并发"访问异常
不要以为只有多线程才有并发访问问题,其实单线程也有。举个例子,对于集合,相信大家经常碰到下面这种异常:
- 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。