通過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機制一定會發生。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章