前言:最近看 java 集合方面的源碼,瞭解到集合使用了 fail-fast 的機制,這裏就記錄一下這個機制是什麼,有什麼用,如何實現的。java
fail-fast 機制,即快速失敗機制,是java集合(Collection)中的一種錯誤檢測機制。當在迭代集合的過程當中該集合在結構上發生改變的時候,就有可能會發生 fail-fast,即拋出 ConcurrentModificationException 異常。fail-fast 機制並不保證在不一樣步的修改下必定會拋出異常,它只是盡最大努力去拋出,因此這種機制通常僅用於檢測 bug。 設計模式
fail-fast 機制出如今 java 集合的 ArrayList、HashMap 等,在多線程或者單線程裏面都有可能出現快速報錯,即出現 ConcurrentModificationException 異常。數組
爲了檢測在迭代集合的過程當中,這個集合是否發生了增長、刪除等(add、remove、clear)使結構發生變化的事。多線程
這個單測跑下來是成功的,下面有講解爲何。ide
import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.util.ArrayList; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.List; /** * @author yule * @date 2018/9/13 17:05 */ public class ArrayListTest { private List<Integer> list = null; @Before public void buildList(){ list = new ArrayList<>(); for(int i = 0; i < 10; i++){ list.add(i); } Assert.assertEquals("[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]", list.toString()); } @Test public void testFor(){ for(int i = 0; i < list.size(); i++){ if(i == 2){ //這個會刪除成功,這裏沒有 fail-fast 的機制 list.remove(2); } } Assert.assertEquals("[0, 1, 3, 4, 5, 6, 7, 8, 9]", list.toString()); } @Test(expected = ConcurrentModificationException.class) public void testForEach(){ for(int x : list){ if(x == 2){ //這個拋出異常,這裏就是 fail-fast 的機制,畢竟 foreach 底層就是 Iterator list.remove(2); } } } @Test(expected = ConcurrentModificationException.class) public void testIterator(){ Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()){ int x = iterator.next(); if(x == 2){ //這個拋出異常,這裏就是 fail-fast 的機制 list.remove(2); } } } @Test public void testIteratorSuccess(){ Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()){ int x = iterator.next(); if(x == 2){ //這個會成功,不會拋出異常,這裏也是 fail-fast 的機制,不知道這裏爲何會成功,能夠繼續看下去 iterator.remove(); } } Assert.assertEquals("[0, 1, 3, 4, 5, 6, 7, 8, 9]", list.toString()); } }
第一個 testFor() 單測成功,是由於普通的 for 循環沒有 fail-fast 機制,由於 fail-fast 機制只針對迭代集合的過程。測試
第二個 testForEach() 單測拋異常,是由於 forEach 底層就是使用了迭代器,其緣由和 testIterator() 單測一致。ui
第三個 testIterator() 單測拋異常,是由於 ArrayList.remove() 方法只修改了 modCount++,而沒有修改 Itr 的 expectedModCount。(詳見下面原理)this
第四個 testIteratorSuccess() 單測成功,是由於 ArrayList 的迭代器的 remove() 方法不只僅是修改了 modCount,也修改了 Itr 的 expectedModCount。(詳見下面原理)spa
首先,必須瞭解 fail-fast 兩個關鍵的東西,ArrayList 的 modCount 和 ArrayList.Itr 內部類的 expectedModCount。.net
其中,modCount 是抽象類 AbstractList 中的變量,默認爲 0,而 ArrayList 繼承了 AbstractList ,因此也有這個變量,modCount 用於記錄集合操做過程當中做的修改次數,並非 size。每次 add 或者 remove modCount 都會 ++。
其中,expectedModCount 是 ArrayList 內部類 Itr 的成員變量,初始值爲 modCount。執行迭代器的 remove、add 方法,都會先執行 ArrayList 的 remove、add 方法(modCount++),而後會執行 expectedModCount = modCount。
上面說到 fail-fast 只針對 迭代器,因此須要知道 ArrayList 的迭代器的實現源碼:
/** * Returns an iterator over the elements in this list in proper sequence. * * <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>. * * @return an iterator over the elements in this list in proper sequence */ public Iterator<E> iterator() { return new Itr(); }
ArrayList 的 內部類 Itr 實現了 Iterator 接口,源碼以下:
/** * An optimized version of AbstractList.Itr */ 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 // 這個就是 fail-fast 判斷的關鍵變量,初始值就爲ArrayList中的modCount 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(); } }
其中,cursor 是指集合遍歷過程當中的即將遍歷的元素的索引。
lastRet 是 cursor - 1,默認爲 -1,即不存在上一個時,爲 -1,它主要用於記錄剛剛遍歷過的元素的索引。
迭代器迭代結束的標誌就是 hasNext() 返回 false,而該方法就是用 cursor 遊標和 size (集合中的元素數目)進行對比,當 cursor 等於 size 時,表示已經遍歷完成。
其實就是指 size 有變更,好比 ArrayList 的 add 和 delete、clear。
迭代集合的過程指的就是使用代迭代器 Iterator 或者 forEach 語法,實際上一個類要使用 forEach 就必須實現 Iterable 接口而且重寫它的 Iterator 方法因此 forEach 在本質上仍是使用的 Iterator。由於 forEach 原理:在編譯的時候編譯器會自動將對 for 這個關鍵字的使用轉化爲對目標的迭代器的使用。
因此得出結論:
一、ArrayList 之因此能使用 foreach 循環遍歷,是由於 ArrayList 全部的 List 都是 Collection 的子接口,而 Collection 是 Iterable 的子接口,ArrayList 的父類 AbstractList 正確地實現了 Iterable 接口的 iterator 方法。
二、任何一個集合,不管是 JDK 提供的仍是本身寫的,只要想使用 foreach 循環遍歷,就必須正確地實現 Iterable 接口
實際上,這種作法就是23中設計模式中的迭代器模式。
其實:Java 將對於數組的 foreach 循環轉換爲對於這個數組每個的循環引用。
因此結論爲:編譯器對集合的 forEach 會調用集合的 迭代器;對數組的 forEach 會調用數組的 for 循環。
測試:java文件
編譯後:class 文件
參考:https://blog.csdn.net/zymx14/article/details/78394464