007Java集合012快速失敗機制和安全失敗機制
注意:本文基於JDK1.8進行記錄。
1 快速失敗機制
1.1 說明
快速失敗機制,即fail-fast機制,直接在容器上進行遍歷,在遍歷過程中一旦發現集合的結構發生改變,就會丟擲ConcurrentModificationException異常導致遍歷失敗。
java.util包下的集合類都是快速失敗機制的,常見的的使用fail-fast方式遍歷的容器有ArrayList和HashMap等。
fail-fast機制不能保證在不同步的修改下一定會丟擲異常,它只是盡最大努力丟擲,因此這種機制一般用於檢測BUG。
1.2 現象
觸發fail-fast機制的案例:
1 public staticvoid main(String[] args) { 2 List<String> list = new ArrayList<>(); 3 list.add("00"); 4 list.add("11"); 5 list.add("22"); 6 for (String s : list) { 7 if ("00".equals(s)) { 8 list.add("99"); 9 } 10 } 11 }
注意:此處只是列舉了一個普通案例,實際上在List、Set、Map中都會發生,並且在單執行緒和多執行緒環境下都會發生。
1.3 原因
fail-fast機制在單執行緒和多執行緒環境中均可發生,倘若在迭代遍歷過程中檢測到集合結構有變化,就有可能觸發並丟擲異常。
想要理解fail-fast機制,就需要檢視底層原始碼的邏輯,因為引發fail-fast機制的原理是一樣的,本文以ArrayList為例進行分析。
檢視ArrayList的迭代器方法:
1 public Iterator<E> iterator() { 2 return new Itr(); 3 }
繼續檢視ArrayList維護的內部類Itr,需要重點關注三個屬性:
1 private class Itr implements Iterator<E> {2 int cursor; // 遍歷集合時即將遍歷的索引 3 int lastRet = -1; // 記錄剛剛遍歷的索引,-1不是不存在上一個元素 4 int expectedModCount = modCount;// 初始值為modCount,用於記錄集合的修改次數 5 6 public boolean hasNext() { 7 return cursor != size;// 判斷遍歷是否結束 8 } 9 10 @SuppressWarnings("unchecked") 11 public E next() { 12 checkForComodification();// 檢查是否觸發fail-fast機制 13 int i = cursor; 14 if (i >= size) 15 throw new NoSuchElementException(); 16 Object[] elementData = ArrayList.this.elementData; 17 if (i >= elementData.length) 18 throw new ConcurrentModificationException(); 19 cursor = i + 1; 20 return (E) elementData[lastRet = i]; 21 } 22 ... 23 final void checkForComodification() { 24 if (modCount != expectedModCount) 25 // expectedModCount在初始化後並未發生改變,那麼如果modCount發生改變,就會丟擲異常 26 throw new ConcurrentModificationException(); 27 } 28 }
通過分析迭代器原始碼可以發現,迭代器的checkForComodification方法是判斷是否要觸發fail-fast機制的關鍵。
在checkForComodification方法中可以看到,是否要丟擲異常在於modCount是否發生改變。
檢視ArrayList原始碼,發現modCount的改變發生在對集合修改中,比如add操作。
所以當在使用迭代器遍歷集合時,如果同時對集合進行了修改,導致modCount發生改變,就會觸發fail-fast機制,丟擲異常。
1.4 解決
1.4.1 使用迭代器提供的方法
為了避免觸發fail-fast機制,在迭代集合時,需要使用迭代器提供的修改方法修改集合。
1.4.2 使用執行緒安全的集合類
也可以使用執行緒安全的集合類,使用CopyOnWriteArrayList代替ArrayList,使用ConcerrentHashMap代替HashMap。
2 安全失敗機制
2.1 說明
安全失敗機制,即fail-safe機制,在集合的克隆物件上進行遍歷,對集合的修改不會影響遍歷操作。
java.util.concurrent包下的集合類都是安全失敗的,可以在多執行緒下併發使用併發修改,常見的的使用fail-safe方式遍歷的容器有CopyOnWriteArrayList和ConcerrentHashMap等。
基於克隆物件的遍歷避免了在修改集合時丟擲ConcurrentModificationException異常,但同樣導致遍歷時不能訪問修改後的內容。