
一、fail-fast机制的定义fail-fast是java集合Collection中的一种错误机制。在多线程环境下当一个线程正在读取集合数据时另一个线程对集合数据进行修改添加、修改、删除、清空等操作就会抛出ConcurrentModificationException异常触发fail-fast事件。二、fail-fast举例程序员不骗程序员直接上代码两个线程类一个读取ArrayList里面的数据一个往arrayList里面插入数据。1、ThreadReadpackage multithread.fastfail; import utils.ThreadUtils; import java.util.Iterator; import java.util.List; public class ThreadRead extends Thread{ ListString list; public ThreadRead(ListString list){ this.listlist; } Override public void run() { int i 0; while (i20){ String value null; Iterator iter list.iterator(); StringBuffer buffer new StringBuffer(); while(iter.hasNext()) { value (String)iter.next(); buffer.append(ThreadRead读取元素value, ); } System.out.println(buffer.toString()); i; } } }2、ThreadWritepackage multithread.fastfail; import utils.ThreadUtils; import java.util.List; public class ThreadWrite extends Thread{ ListString list; public ThreadWrite(ListString list){ this.listlist; } Override public void run() { int i 10; while (i20){ list.add(String.valueOf(i)); System.out.println(ThreadWrite插入元素i, ); i; } } }3、测试类package multithread.fastfail; import java.util.ArrayList; import java.util.List; public class FastFailTest { private static ListString list new ArrayListString(); static{ list.add(1); list.add(2); list.add(3); } public static void main(String[] args) { new ThreadRead(list).start(); new ThreadWrite(list).start(); } }运行结果三、异常分析上述案例中我们是利用两个线程分别向ArrayList里面分别读和写数据所以我们进行异常分析时可以把焦点定位在ArrayList这个类中。我们发现这个问题主要是在读取数据时抛出那么俺们从ThreadRead中可以看到涉及到ArrayList类相关的方法就是被红框标记出来的三处。首先我们看iterator方法查看源码得知iterator时返回Itr这个对象。Itr是一个内部类它初始是有个重要的设定expectedModCount modCount。modCount是ArrayList被修改的次数。private class Itr implements IteratorE { int cursor; // index of next element to return int lastRet -1; // index of last element returned; -1 if no such int expectedModCount 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(); } }hasNext方法只是判断下一个元素与游标的关系异常不会发生在这个方法。next方法会调用checkForComodification方法来比较expectedModCount 和 modCount的大小如果发现不等则返回ConcurrentModificationException异常。到此为止我们就找到了抛出异常是因为执行Itr.next()时发现expectedModCount 和 modCount不一致的情况。而且Itr内remove方法完成操作后也会设置expectedModCount 和 modCount相等。既然问题不是出现在ThreadRead中我们再看ThreadWrite。涉及到ArrayList的就是红框所在的add方法。我们回到ArrayList源码中涉及到add方法及辅助方法中ensureExplicitCapacity会进modCount增一的操作。/** * Appends the specified element to the end of this list. * * param e element to be appended to this list * return tttrue/tt (as specified by {link Collection#add}) */ public boolean add(E e) { ensureCapacityInternal(size 1); // Increments modCount!! elementData[size] e; return true; } private void ensureCapacityInternal(int minCapacity) { if (elementData DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount; // overflow-conscious code if (minCapacity - elementData.length 0) grow(minCapacity); }最终梳理的出抛错的流程ThreadRead获取ArrayList迭代器Itr时modCount获取到当前的快照值并赋值给expectedModCount假设为10此时ThreadWrite对ArrayList通过add方法添加元素并把modCount进行1操作这次modCount值为11。ThreadRead对Itr调用其next方法时检查发现expectedModCount和modCount不一致因此抛出了ConcurrentModificationException异常触发了fail-fast机制。四、解决方案1、解决方案从上面我们从源码层面梳理了fail-fast机制那么是否有办法解决呢答案是肯定的官方建议使用“java.util.concurrent包下的类”去取代“java.util包下的类。如上述案例中把ArrayList换成CopyOnWriteArrayList即可避免fail-fast。测试类import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; public class FastFailTest { // private static ListString list new ArrayListString(); private static ListString list new CopyOnWriteArrayListString(); static{ list.add(1); list.add(2); list.add(3); } public static void main(String[] args) { new ThreadRead(list).start(); new ThreadWrite(list).start(); } }运行结果D:\Program Files\Java\jdk1.8.0_40\bin\java.exe -javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2022.1\lib\idea_rt.jar53500:D:\Program Files\JetBrains\IntelliJ IDEA 2022.1\bin -Dfile.encodingUTF-8 -classpath D:\Program Files\Java\jdk1.8.0_40\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_40\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_40\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_40\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_40\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_40\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_40\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_40\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_40\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_40\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_40\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_40\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_40\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_40\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_40\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_40\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_40\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_40\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_40\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_40\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_40\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_40\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_40\jre\lib\rt.jar;D:\program\JavaPogram\target\classes;C:\Users\ethan\.m2\repository\cglib\cglib\3.2.5\cglib-3.2.5.jar;C:\Users\ethan\.m2\repository\org\ow2\asm\asm\5.2\asm-5.2.jar;C:\Users\ethan\.m2\repository\org\apache\ant\ant\1.9.6\ant-1.9.6.jar;C:\Users\ethan\.m2\repository\org\apache\ant\ant-launcher\1.9.6\ant-launcher-1.9.6.jar multithread.fastfail.FastFailTest ThreadRead读取元素1, ThreadRead读取元素2, ThreadRead读取元素3, ThreadRead读取元素1, ThreadRead读取元素2, ThreadRead读取元素3, ThreadRead读取元素1, ThreadRead读取元素2, ThreadRead读取元素3, ThreadRead读取元素1, ThreadRead读取元素2, ThreadRead读取元素3, ThreadRead读取元素1, ThreadRead读取元素2, ThreadRead读取元素3, ThreadWrite插入元素10, ThreadRead读取元素1, ThreadRead读取元素2, ThreadRead读取元素3, ThreadRead读取元素10, ThreadWrite插入元素11, ThreadRead读取元素1, ThreadRead读取元素2, ThreadRead读取元素3, ThreadRead读取元素10, ThreadRead读取元素11, ThreadWrite插入元素12, ThreadRead读取元素1, ThreadRead读取元素2, ThreadRead读取元素3, ThreadRead读取元素10, ThreadRead读取元素11, ThreadRead读取元素12, ThreadWrite插入元素13, ThreadRead读取元素1, ThreadRead读取元素2, ThreadRead读取元素3, ThreadRead读取元素10, ThreadRead读取元素11, ThreadRead读取元素12, ThreadRead读取元素13, ThreadWrite插入元素14, ThreadRead读取元素1, ThreadRead读取元素2, ThreadRead读取元素3, ThreadRead读取元素10, ThreadRead读取元素11, ThreadRead读取元素12, ThreadRead读取元素13, ThreadRead读取元素14, ThreadWrite插入元素15, ThreadRead读取元素1, ThreadRead读取元素2, ThreadRead读取元素3, ThreadRead读取元素10, ThreadRead读取元素11, ThreadRead读取元素12, ThreadRead读取元素13, ThreadRead读取元素14, ThreadRead读取元素15, ThreadWrite插入元素16, ThreadRead读取元素1, ThreadRead读取元素2, ThreadRead读取元素3, ThreadRead读取元素10, ThreadRead读取元素11, ThreadRead读取元素12, ThreadRead读取元素13, ThreadRead读取元素14, ThreadRead读取元素15, ThreadRead读取元素16, ThreadWrite插入元素17, ThreadRead读取元素1, ThreadRead读取元素2, ThreadRead读取元素3, ThreadRead读取元素10, ThreadRead读取元素11, ThreadRead读取元素12, ThreadRead读取元素13, ThreadRead读取元素14, ThreadRead读取元素15, ThreadRead读取元素16, ThreadRead读取元素17, ThreadWrite插入元素18, ThreadRead读取元素1, ThreadRead读取元素2, ThreadRead读取元素3, ThreadRead读取元素10, ThreadRead读取元素11, ThreadRead读取元素12, ThreadRead读取元素13, ThreadRead读取元素14, ThreadRead读取元素15, ThreadRead读取元素16, ThreadRead读取元素17, ThreadRead读取元素18, ThreadWrite插入元素19, ThreadRead读取元素1, ThreadRead读取元素2, ThreadRead读取元素3, ThreadRead读取元素10, ThreadRead读取元素11, ThreadRead读取元素12, ThreadRead读取元素13, ThreadRead读取元素14, ThreadRead读取元素15, ThreadRead读取元素16, ThreadRead读取元素17, ThreadRead读取元素18, ThreadRead读取元素19, ThreadRead读取元素1, ThreadRead读取元素2, ThreadRead读取元素3, ThreadRead读取元素10, ThreadRead读取元素11, ThreadRead读取元素12, ThreadRead读取元素13, ThreadRead读取元素14, ThreadRead读取元素15, ThreadRead读取元素16, ThreadRead读取元素17, ThreadRead读取元素18, ThreadRead读取元素19, ThreadRead读取元素1, ThreadRead读取元素2, ThreadRead读取元素3, ThreadRead读取元素10, ThreadRead读取元素11, ThreadRead读取元素12, ThreadRead读取元素13, ThreadRead读取元素14, ThreadRead读取元素15, ThreadRead读取元素16, ThreadRead读取元素17, ThreadRead读取元素18, ThreadRead读取元素19, ThreadRead读取元素1, ThreadRead读取元素2, ThreadRead读取元素3, ThreadRead读取元素10, ThreadRead读取元素11, ThreadRead读取元素12, ThreadRead读取元素13, ThreadRead读取元素14, ThreadRead读取元素15, ThreadRead读取元素16, ThreadRead读取元素17, ThreadRead读取元素18, ThreadRead读取元素19, ThreadRead读取元素1, ThreadRead读取元素2, ThreadRead读取元素3, ThreadRead读取元素10, ThreadRead读取元素11, ThreadRead读取元素12, ThreadRead读取元素13, ThreadRead读取元素14, ThreadRead读取元素15, ThreadRead读取元素16, ThreadRead读取元素17, ThreadRead读取元素18, ThreadRead读取元素19, ThreadRead读取元素1, ThreadRead读取元素2, ThreadRead读取元素3, ThreadRead读取元素10, ThreadRead读取元素11, ThreadRead读取元素12, ThreadRead读取元素13, ThreadRead读取元素14, ThreadRead读取元素15, ThreadRead读取元素16, ThreadRead读取元素17, ThreadRead读取元素18, ThreadRead读取元素19, Process finished with exit code 02、CopyOnWriteArrayList避免fail-fast的原理CopyOnWrite**相关类的实现原理主要是读写分离最后把引用指向新容器。读的操作是在原容器中进行但是改的操作则在内存中新建一个副本并在其上面进行。当完成操作后会把引用从旧的对象中指向新的容器。CopyOnWrite类容器并发读不需要加锁只有写的时候才会进行加锁提升了效率。