Java集合之fail-fast

13 篇文章 0 订阅
11 篇文章 0 订阅

1 fail-fast简介

  • fail-fast是java集合(Collection)的一种错误机制
  • 当多个线程对同一个集合的内容进行操作时,就可能产生fail-fast事件

 2 代码测试

package basicKnowledge.集合框架.arrayList;

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

/**
 * @program:summary
 * @author:peicc
 * @create:2019-07-19 15:23:29
 **/
//测试fast-fail机制
public class Test_fast_fail {
    static ArrayList<Integer> list=new ArrayList<Integer>();

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<10;i++){
                    list.add(i);
                    printAll();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=10;i<20;i++){
                    list.add(i);
                    printAll();
                }
            }
        }).start();
    }
    private static void printAll(){
        Iterator iterator=list.iterator();
        while(iterator.hasNext()){
            System.out.print(iterator.next()+",");
        }
    }
}

运行结果

"C:\Program Files\Java\jdk1.8.0_191\bin\java.exe" 
0,0,10,0,10,11,0,10,11,12,0,10,11,12,13,0,10,11,12,13,14,0,10,11,12,13,14,15,0,10,11,12,13,14,15,16,0,10,11,12,13,14,15,16,17,0,10,11,12,13,14,15,16,17,18,0,10,11,12,13,14,15,16,17,18,19,Exception in thread "Thread-0" Disconnected from the target VM, address: '127.0.0.1:56115', transport: 'socket'
java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
	at basicKnowledge.集合框架.arrayList.Test_fast_fail.printAll(Test_fast_fail.java:38)
	at basicKnowledge.集合框架.arrayList.Test_fast_fail.access$000(Test_fast_fail.java:12)
	at basicKnowledge.集合框架.arrayList.Test_fast_fail$1.run(Test_fast_fail.java:21)
	at java.lang.Thread.run(Thread.java:748)

Process finished with exit code 0

 3 fast-fail原理

  • fast-fail是在操作Iterator时产生的
  • Iterator在执行next()以及remove()时,都会执行checkForComodifacation
  • 在checkForComodifacation()方法中,当modCount !=expectedModCount时,抛出ConcurrentModificationException异常,产生fail-fast事件
  • 什么时候modCount !=expectedModCount?
  • expectedModCount 在创建Itr对象时,被赋值为 modCount。所以expectedModCount 不可能修改
  • 因此,问题出现在modCount上。接下来,需要考虑的问题是,modCount什么时候会被修改?
  • 通过查看源码,可以知道,add()、remove(),还是clear(),只要涉及到修改集合中的元素个数时,都会改变modCount的值

4 解决办法 

  • 使用“java.util.concurrent包下的类”去取代“java.util包下的类”

5 解决实现原理

  • 以和ArrayList对应的CopyOnWriteArrayList进行说明 

首先看下Iterator的源码 

    //iterator()函数
    public Iterator<E> iterator() {
        return new COWIterator<E>(getArray(), 0);
    }
  • 通过构造函数将数组引用传递给迭代器
 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++];
        }

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

        /**
         * Not supported. Always throws UnsupportedOperationException.
         * @throws UnsupportedOperationException always; {@code remove}
         *         is not supported by this iterator.
         */
        public void remove() {
            throw new UnsupportedOperationException();
        }

        /**
         * Not supported. Always throws UnsupportedOperationException.
         * @throws UnsupportedOperationException always; {@code set}
         *         is not supported by this iterator.
         */
        public void set(E e) {
            throw new UnsupportedOperationException();
        }

        /**
         * Not supported. Always throws UnsupportedOperationException.
         * @throws UnsupportedOperationException always; {@code add}
         *         is not supported by this iterator.
         */
        public void add(E e) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void forEachRemaining(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            Object[] elements = snapshot;
            final int size = elements.length;
            for (int i = cursor; i < size; i++) {
                @SuppressWarnings("unchecked") E e = (E) elements[i];
                action.accept(e);
            }
            cursor = size;
        }
    }
  • 构造方法中,将引用复制一份给快照 
  • 数组引用为什么要复制一份给快照?
  • 因为之后原数组引用会指向新数组,如果不保存原数组引用的话,此时迭代器操作的将是新数组

再来看下 CopyOnWriteArrayList相关操作的源码(add、remove等)

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    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一份原数组,然后在copy的数组上进行操作,最后将copy的引用赋值给原数组
  • 而迭代器操作的数组是在调用Iterator()方法时传递进去原数组(下图中上面的数组),但是可能操作的并不是最新的数据
  • 示意图如下

 

参考文献

1.JDK1.8 源码

2.Java 集合系列04之 fail-fast总结(通过ArrayList来说明fail-fast的原理、解决办法)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值