Java集合系列04之fail-fast機制分析

系列文章

前言

fail-fast機制是Java集合的一種錯誤機制,當多個線程對同一集合內(nèi)容操作時,便可能會出現(xiàn)fail-fast現(xiàn)象。例如:存在兩個線程(A、B),A通過Iterator在遍歷集合C中的元素過程中,B修改了集合C的內(nèi)容,程序就可能會拋出 ConcurrentModificationException 異常,出現(xiàn)fail-fast現(xiàn)象。

本文源碼分析基于jdk 1.8.0_121

實例展示

下面給出一個實際代碼展示,來演示fail-fast現(xiàn)象,如下:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Main {

    private static List<Integer> list = new ArrayList<>();

    public static void main(String[] args) {
        for (int i= 0; i<5; i++){
            list.add(i);
        }
        new ThreadA().start();
        new ThreadB().start();
    }

    static class ThreadA extends Thread{
        public void run(){
            Iterator iter = list.iterator();
            int value = 0;
            while(iter.hasNext()) {
                value = (int)iter.next();
                System.out.println(value+", ");
                try{
                    Thread.sleep(10);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }

    static class ThreadB extends Thread{
        public void run(){
            for (int i = 5;i<10;i++){
                list.add(i);
            }
        }
    }
}

運行以上代碼,結(jié)果如下:

0,
Exception in thread "Thread-0" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at Main$ThreadA.run(Main.java:22)

需要注意的是,如果我們再加一個子線程C,其代碼如下:

static class ThreadC extends Thread{
    public void run(){
        for (int i = 0;i<5;i++){
            list.set(i,i+1);
        }
    }
}

new ThreadA().start();
new ThreadC().start();

當我們同時運行子線程A和子線程C時,程序可以順利執(zhí)行,因為子線程沒有改變原有list的結(jié)構(gòu),只是重新給每個索引設(shè)置了新的值,運行結(jié)果如下:

0, 
2, 
3, 
4, 
5, 

解決辦法

對于前面的集合ArrayList其不是線程安全的,LinkedList也不是線程安全的,在多線程的環(huán)境下,我們可以使用java.util.concurrent包下的類去取代java.util包下的類來避免并發(fā)帶來的fail-fast現(xiàn)象;對于ArrayList我們可以使用CopyOnWriteArrayList代替即可。

fail-fast原理

當操作Iterator時,如果拋出ConcurrentModificationException便出現(xiàn)了fail-fast現(xiàn)象,而ArrayListiterator方法返回一個Itr對象,Itr類是ArrayList的內(nèi)部類:

// Itr是迭代器的實現(xiàn)類
private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    
    // 修改次數(shù)的記錄值
    // 遍歷list元素時,會比較expectedModCount和modCount是否相等
    // 如果不相等,則會拋出異常,出現(xiàn)fail-fast現(xiàn)象
    int expectedModCount = modCount;

    public boolean hasNext() {
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        // 判斷expectedModCount和modCount是否相等
        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];
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public void forEachRemaining(Consumer<? super E> consumer) {
        Objects.requireNonNull(consumer);
        final int size = ArrayList.this.size;
        int i = cursor;
        if (i >= size) {
            return;
        }
        final Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length) {
            throw new ConcurrentModificationException();
        }
        while (i != size && modCount == expectedModCount) {
            consumer.accept((E) elementData[i++]);
        }
        // update once at end of iteration to reduce heap write traffic
        cursor = i;
        lastRet = i - 1;
        checkForComodification();
    }
    
    // 不相等,拋出異常
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

modCountAbstractList中的一個成員變量,而ArrayList繼承自AbstractList,ArrayList的以下方法會修改modCount

public void trimToSize() {
    modCount++;
    if (size < elementData.length) {
        elementData = (size == 0)
          ? EMPTY_ELEMENTDATA
          : Arrays.copyOf(elementData, size);
    }
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

public E remove(int index) {
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work
}


public void clear() {
    modCount++;

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

    size = 0;
}

protected void removeRange(int fromIndex, int toIndex) {
    modCount++;
    int numMoved = size - toIndex;
    System.arraycopy(elementData, toIndex, elementData, fromIndex,
                     numMoved);

    // clear to let GC do its work
    int newSize = size - (toIndex-fromIndex);
    for (int i = newSize; i < size; i++) {
        elementData[i] = null;
    }
    size = newSize;
}

...

從以上我們可以發(fā)現(xiàn),當涉及到修改ArrayList中元素個數(shù)的方法(remove(),clear(),ensureExplicitCapacity()...),便會修改modCount

因此在前文演示代碼中,子線程A正在通過迭代器遍歷集合元素,而子線程B中add()方法修改了集合中元素的數(shù)量,導(dǎo)致此時expectedModCountmodCount不相等,因此執(zhí)行checkForComodification方法時,便會拋出異常。

CopyOnWriteArrayList源碼分析

前文說了CopyOnWriteArrayList可以解決集合面對并發(fā),下面我們從源碼角度分析下CopyOnWriteArrayList

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    
    ...
    
    public Iterator<E> iterator() {
        return new COWIterator<E>(getArray(), 0);
    }
    
    ...
    static final class COWIterator<E> implements ListIterator<E> {
    
        // 創(chuàng)建COWIterator時,將集合中的元素保存到一個新的數(shù)組
        // 當原始集合的數(shù)據(jù)改變,拷貝數(shù)據(jù)中的值也不會變化
        private final Object[] snapshot;
        /** Index of element to be returned by subsequent call to next.  */
        private int cursor;
        
        private COWIterator(Object[] elements, int initialCursor) {
            cursor = initialCursor;
            snapshot = elements;
        }
        
        public boolean hasNext() {
            return cursor < snapshot.length;
        }
        
        public boolean hasPrevious() {
            return cursor > 0;
        }
        
        @SuppressWarnings("unchecked")
        public E next() {
            if (! hasNext())
                throw new NoSuchElementException();
            return (E) snapshot[cursor++];
        }
        
        @SuppressWarnings("unchecked")
        public E previous() {
            if (! hasPrevious())
                throw new NoSuchElementException();
            return (E) snapshot[--cursor];
        }
        
        public int nextIndex() {
            return cursor;
        }
        
        public int previousIndex() {
            return cursor-1;
        }
        
        // 不支持的方法
        public void remove() {
            throw new UnsupportedOperationException();
        }
        
        // 不支持的方法
        public void set(E e) {
            throw new UnsupportedOperationException();
        }
        
        // 不支持的方法
        public void add(E e) {
            throw new UnsupportedOperationException();
        }
        
        ...
        
    }
}

從以上代碼,我們可以看到以下幾點:

  • CopyOnWriteArrayList沒有繼承AbstractList,只是實現(xiàn)了List接口
  • ArrayList返回的Iterator是在AbstractList中實現(xiàn)的,而CopyOnWriteArrayList是自己實現(xiàn)的Iterator
  • CopyOnWriteArrayList的各個方法中不會拋出ConcurrentModificationException異常

參考內(nèi)容

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容