美文网首页
30分钟掌握Fail-Fast机制 | 不信试一试

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

作者: 采风JS | 来源:发表于2017-06-27 22:08 被阅读0次

    假如有两个线程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
        }
    
    • 对ArrayList进行迭代时,会不断检测expectedCount和modCount的值,如果其他操作修改了全局的modCount的值,则会报出异常;

    三、解决方案

    • 推荐使用CopyOnWriteArrayList,先来解读一下源码:
        // 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数组,不会改变迭代器的对象,修改完成后改变原有数据的引用即可。

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

    相关文章

      网友评论

          本文标题:30分钟掌握Fail-Fast机制 | 不信试一试

          本文链接:https://www.haomeiwen.com/subject/uqqycxtx.html