30分钟掌握Fail-Fast机制 | 不信试一试

2017-06-27  本文已影响0人  采风JS

假如有两个线程A与线程B,线程A对集合C进行迭代操作时,线程B改变了集合C的数据结构,此时会报出ConcurrentModificationException异常,这就是Fail-Fast(快速失败),其为java中集合操作的快速检错机制。留下30分钟,一看究竟。

一、Fail-Fast案例

public class FailFastTest {
    private static List<Integer> list = new ArrayList<Integer>();

    private static class ThreadOne extends Thread{
        public void run(){
            Iterator<Integer> it = list.iterator();
            while(it.hasNext()){
                Integer i = it.next();
                System.out.println("ThreadOne is using : "+ i);
                try {
                    Thread.sleep(5);
                } catch (Exception e) {
                    e.getStackTrace();
                }
            }
        }
    }
    
    private static class ThreadTwo extends Thread{
        public void run(){
            int i = 0;
            while(i < 6){
                if (i == 3 ) {
                    list.remove(i);
                }
                i++;
            }
        }
    }
    
    public static void main(String[] args) {
        for(int i=0 ; i < 10; i++ ){
            list.add(i);
        }
        new ThreadOne().start();
        new ThreadTwo().start();
    }
}

// 程序运行后,报出异常,因为ThreadOne进行迭代操作时,ThreadTwo对其进行数据结构的修改,程序报出异常提醒

二、源码解读

    // JDK1.8内ArrayList中迭代器源码
    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
        int expectedModCount = modCount; // 由modCount决定,值不会改变

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

        @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];
        }

        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();
        }
    }

// 在迭代器中的next()、remove()和forEachRemaining()方法中
// 均包含checkForComodification(),而checkForComodification() // 方法
// 会抛出ConcurrentModificationException()异常,
// 关键在于检测modCount与expectedModCount是否相同
    //JDK1.8内ArrayList中包含的方法,大都包含modCount++操作
    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
    }

三、解决方案

    // JDK1.8内CopyOnWriteArrayList中迭代器不进行expectedCount和modCount检查
    static final class COWIterator<E> implements ListIterator<E> {
        /** Snapshot of the array */
        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++];
        }
    // JDK1.8内CopyOnWriteArrayList中迭代器不进行expectedCount和modCount检查
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

// add()方法,本质是对于原数组的copy,不会存在修改集合数据结构的操作,其他方法类似

CopyOnWriteArrayList的核心原理:对于Array的操作(增删改查),都是基于复制到新copy数组,不会改变迭代器的对象,修改完成后改变原有数据的引用即可。

今天就暂时到这里啦!毕业季在匆忙与顾念中离去,回首一年前的毕业季,做好眼前事,珍惜校园时光。

上一篇 下一篇

猜你喜欢

热点阅读