通过ArrayList对modCount的操作分析fail-fast 机制

AbstractList类中有一个属性

protected transient int modCount = 0;

api中对它的描述是:

  • 此列表已被结构修改的次数。 结构修改是改变列表大小的那些修改,或以其他方式扰乱它,使得正在进行的迭代可能产生不正确的结果。
  • 该字段由迭代器和列表迭代器实现使用,由iteratorlistIterator方法返回。 如果该字段的值意外更改,迭代器(或列表迭代器)将抛出一个ConcurrentModificationException响应next , remove , previous , setadd操作。 这提供了fail-fast行为,而不是面对在迭代期间的并发修改的非确定性行为

我的理解是,modCount表示了当前列表结构被修改的次数,在调用迭代器操作时,则会检查这个值,如果发现已更改,抛出异常

不过需要注意的是transient修饰意味着这个属性不会被序列化,而且modCount并没有被voliate修饰,也就是它不能保证在线程之间是可见的

 

从ArraytList源码中可以发现,add,remove,clear等方法实现时,均添加了modCount++;操作,例如clear方法:

public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

在arraylist中调用迭代器是通过内部类实现的:

public Iterator<E> iterator() {
        return new Itr();
    }

在这个内部类中,同样维护了一个类似modCount的变量

int expectedModCount = modCount;

并提供了检测方法

final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

这个检测方法在迭代器中类似next方法里面作为首先需要判断的条件

@SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

我们综合以上,就可以得出,

在使用迭代器遍历arraylist时,会初始化一个和modCount相等的变量,如果在迭代过程中,arraylist中发生了类似add这种改变结构的操作(modCount改变),导致modCount != expectedModCount,那么会抛出一个异常ConcurrentModificationException,即产生fail-fast事件

 

产生fail-fast有两种原因:

1.单线程情况下,迭代的过程中,调用类似add方法,但是一般不会这样做

     ArrayList<Integer> al = new ArrayList<>();
        for(int i=0;i<10;i++)
            al.add(i);
        Iterator<Integer> it = al.iterator();
        while(it.hasNext()) {
            System.out.println(it.next());
            if(!it.hasNext())
                al.add(10);
        }    

 

2.主要发生在多线程情况下,例如让线程1迭代,线程2修改,就有可能会出现

        final ArrayList<Integer> al = new ArrayList<>();
        for(int i=0;i<10;i++)
            al.add(i);    
        
        new Thread(new Runnable() {            
            @Override
            public void run() {
                Iterator<Integer> it = al.iterator();
                while(it.hasNext()) {
                    System.out.print(it.next()+" ");
                }                
            }
        }).start();        
        new Thread(new Runnable() {                    
                    @Override
                    public void run() {
                        al.remove(6);        
                    }                    
        }).start();
    

 

如果抛出异常,就类似这个样子:

0 1 Exception in thread "Thread-0" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
    at java.util.ArrayList$Itr.next(ArrayList.java:859)
    at com.rw.importword.utils.Text$1.run(Text.java:19)
    at java.lang.Thread.run(Thread.java:748)

 

但是也有可能不抛出异常:

 

根据输出结果来看,al的结构已经改变,但是没有抛出异常

至于原因我想是:

modCount没有被voliate修饰,在线程之间不可见,可能某一个时机,线程2中remove操作改变的modCount值并没有及时写到内存中,线程1中迭代器获取的modCount值仍然是之前的值

 

由此可以得出,fail-fast机制,是一种错误检测机制。它只能被用来检测错误,因为JDK并不保证fail-fast机制一定会发生。

 

posted @ 2018-09-05 14:49  柠檬水请加冰  阅读(666)  评论(0编辑  收藏  举报