1. 程式人生 > 其它 >007Java集合012快速失敗機制和安全失敗機制

007Java集合012快速失敗機制和安全失敗機制

本文主要了解了Java中的快速失敗機制和安全失敗機制。

注意:本文基於JDK1.8進行記錄。

1 快速失敗機制

1.1 說明

快速失敗機制,即fail-fast機制,直接在容器上進行遍歷,在遍歷過程中一旦發現集合的結構發生改變,就會丟擲ConcurrentModificationException異常導致遍歷失敗。

java.util包下的集合類都是快速失敗機制的,常見的的使用fail-fast方式遍歷的容器有ArrayList和HashMap等。

fail-fast機制不能保證在不同步的修改下一定會丟擲異常,它只是盡最大努力丟擲,因此這種機制一般用於檢測BUG。

1.2 現象

觸發fail-fast機制的案例:

 1 public static
void 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異常,但同樣導致遍歷時不能訪問修改後的內容。