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