Java中的Iterator非常方便地為所有的資料來源提供了一個統一的資料讀取(刪除)的介面,但是在使用的時候容易報如下錯誤ConcurrentModificationException,原因是在使用迭代器時候底層資料被修改,最常見於資料來源不是執行緒安全的類,如HashMap & ArrayList等。如下程式碼所示:
public class FailFastDemo { public static void main(String[] args)throws Exception { List<Object> objects = new ArrayList<Object>(); objects.add("object1"); objects.add("object2"); objects.add("object3"); Iterator<Object> iterator = objects.iterator(); while (iterator.hasNext()) { Object object = iterator.next(); System.out.println(object); objects.remove(object); } } }
產生原因
Java中的集合類分為兩種型別:執行緒安全,位於java.util.concurrent命名目錄下,如CopyOnWriteArrayList;執行緒不安全:位於java.util目錄下,如ArrayList,HashMap。所謂執行緒安全是在多執行緒環境下,這個類還能表現出和行為規範一致的結果。既然有執行緒安全的集合,為什麼還要使用執行緒非安全的集合呢呢?因為執行緒安全的類通常需要通過各種手段去保持對資料訪問的同步,會降低資料訪問的效率。使用非執行緒安全的集合類在非併發場景具有很大的優勢。如果開發者在使用時沒有注意,將非執行緒安全的集合類用在了併發的場景下,比如執行緒A獲取了ArrayList的iterator,然後執行緒B通過呼叫ArrayList.add()修改了ArrayList的資料,此時就有可能會丟擲ConcurrentModificationException。
原理很簡單,構建Iterator時將當前ArrayList的modCount儲存在
expectedModCount之中
,當下一次呼叫next()時,會判斷ArrayList的modCount值和iterator內部expectedModCount
儲存的值是否相等。如果相等,說明在遍歷的過程中集合類內有發生改變;如果不相等說明在遍歷的過程中,集合中的資料發生了改變此時就丟擲ConcurrentModificationException。fast-fail是JDK為了提示開發者將非執行緒安全的類使用到併發的場景下時,丟擲一個異常,及早發現程式碼中的問題。
補充:
fast-fail的Iterator本身也提供了remove()來刪除當前遍歷到的元素,例如:ArrayListIterator中的remove(),可以在迭代過程中刪除當前資料而不丟擲ConcurrentModificationException:
public class FailFastDemo { public static void main(String[] args)throws Exception { List<Object> objects = new ArrayList<Object>(); objects.add("object1"); objects.add("object2"); objects.add("object3"); Iterator<Object> iterator = objects.iterator(); while (iterator.hasNext()) { Object object = iterator.next(); System.out.println(object); iterator.remove(); } } }