fail-fast 机制是Java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操做时,就可能会产生fail-fast(快速失败)事件。例如:当某一个线程A经过iterator去遍历某集合的过程当中,若该集合的内容被其余线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。java
迭代器的快速失败行为没法获得保证,它不能保证必定会出现该错误,可是快速失败操做会尽最大努力抛出ConcurrentModificationException异常。数组
注意:上面所说的是在多线程环境下会发生fail-fast事件,可是单线程条件下若是违反了规则也是会产生fail-fast事件的多线程
在文档中有这么一段话:编写的程序依赖于快速失败机制产生的异常是不对的,迭代器的快速检测机制仅仅用于检测错误。ide
import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * Created with IDEA * * @author DuzhenTong * @Date 2018/3/18 * @Time 17:34 */ public class FailFast { public static void main(String[] args) { List list = new ArrayList(); for (int i = 0; i < 10; i++) { list.add(i); } iterator(list); } public static void iterator(List list) { Iterator it = list.iterator(); int index = 0; while (it.hasNext()) { if (index == 6) { list.remove(index); } index++; System.out.println(it.next()); } } }
输出结果:测试
0 1 2 3 4 5 Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:819) at java.util.ArrayList$Itr.next(ArrayList.java:791) at FailFast.iterator(FailFast.java:29) at FailFast.main(FailFast.java:18)
import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * Created with IDEA * * @author DuzhenTong * @Date 2018/3/18 * @Time 17:59 */ public class FailFast1 { public static List list = new ArrayList(); public static void main(String[] args) { for (int i = 0; i < 10; i++) { list.add(i); } new ThreadA().start(); new ThreadB().start(); } public static class ThreadA extends Thread { @Override public void run() { Iterator it = list.iterator(); while (it.hasNext()) { System.out.println("集合遍历中……:"+it.next()); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static class ThreadB extends Thread { @Override public void run() { int index = 0; while (index != 10) { System.out.println("线程等待中……:"+index); if (index == 3) { list.remove(index); } index++; } } } }
输出结果:this
线程等待中……:0 集合遍历中……:0 线程等待中……:1 线程等待中……:2 线程等待中……:3 线程等待中……:4 线程等待中……:5 线程等待中……:6 线程等待中……:7 线程等待中……:8 线程等待中……:9 Exception in thread "Thread-0" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:819) at java.util.ArrayList$Itr.next(ArrayList.java:791) at FailFast1$ThreadA.run(FailFast1.java:28)
上面的程序已经说明了为何会发生fail-fast事件(快速失败),在多线程条件下,一个线程正在遍历集合中的元素,这时候另外一个线程更改了集合的结构,程序才会抛出ConcurrentModificationException,在单线程条件下也是在遍历的时候,这时候更改集合的结构,程序就会抛出ConcurrentModificationException。
要具体知道为何会出现fail-fast,就要分析源码,fail-fast出现是在遍历集合的时候出现的,也就是对集合进行迭代的时候,对集合进行迭代的时候都是操做迭代器,集合中的内部类:(ArrayList源码).net
private class Itr implements Iterator<E> { int cursor; int lastRet = -1; int expectedModCount = modCount;//---------------------1 public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") public E next() { checkForComodification(); //………此处代码省略………… } 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(); } } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
上面的代码中咱们能够看到抛出ConcurrentModificationException异常,上面的next(),remove()都会调用checkForComodification()方法检查两个变量的值是否相等,不相等就会抛出异常。在上面程序中的数字1处:线程
int expectedModCount = modCount;
modCount的值赋值给expectedModCount,知道modCount这个值的含义是什么?为何会发生改变?缘由就会找到了。
源码点进去,发现这个modCount变量并不在ArrayList类中,而在AbstractList中,做为一个成员变量。code
protected transient int modCount = 0;
接下来分析源码,看最经常使用的方法对象
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
调用的ensureCapacityInternal方法:
private void ensureCapacityInternal(int minCapacity) { modCount++;//modCount自增———————————— // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }
public E remove(int index) { rangeCheck(index); modCount++;//modCount自增—————————————— E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // Let gc do its work return oldValue; }
public void clear() { modCount++;//modCount自增—————————————— // Let gc do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }
分析了源码就知道缘由是什么了,凡是涉及到改变了集合的结构(改变元素的个数)的操做(包括增长,移除或者清空等)modCount这个变量都会自增,在得到迭代对象的时候,先把这个modCount变量赋值给expectedModCount,在迭代的时候每次都会检查这个变量是否与expectedModCount一致,由于若是是在集合中添加或者删除元素modCount的值都会发生改变。
这里在网上看到不少的文章都是这么说的:为何CopyOnWriteArrayList能够作到不会发生fail-fast?由于CopyOnWriteArrayList全部可变操做(add、set 等等)都是经过对底层数组进行一次新的复制来实现的。
能够分析源码(下面的1,2处)
public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray();//————————1 int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1);//————————2 newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); } }
个人见解:缘由不止有CopyOnWriteArrayList的add、set、remove等会改变原数组的方法中,都是先copy一份原来的array,再在copy数组上进行add、set、remove操做,这就才不影响COWIterator那份数组。
为何没有记录修改次数的值或者说不比较modCount也能作到内存的一致性呢?
在上面的代码1处,调用了getArray()方法,看源码:
final Object[] getArray() { return array; }
private volatile transient Object[] array;
由于getArray()返回的array的类型是用volatile修饰的,volatile类型的(强制内存一致性)
具体能够看个人另外一篇关于volatile的:volatile关键字解析
参考文章:http://cmsblogs.com/?p=1220