fail-fast和fail-safe
前言
前段時間公司招的實習生在使用迭代器遍歷的時候,對集合內容進行了修改,從而拋出ConcurrentModificationException. 而後給他講解之餘也整理了這一篇文章.java
fail-fast ( 快速失敗 )
在使用迭代器遍歷一個集合對象時,好比加強for,若是遍歷過程當中對集合對象的內容進行了修改(增刪改),會拋出 ConcurrentModificationException 異常.編程
查看ArrayList源代碼,在next方法執行的時候,會執行checkForComodification()方法安全
@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]; } //...............省略............. final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
原理:多線程
迭代器在遍歷時直接訪問集合中的內容,而且在遍歷過程當中使用一個modCount變量,
集合中在被遍歷期間若是內容發生變化,就會改變modCount的值,
每當迭代器使用 hashNext()/next()遍歷下一個元素以前,都會檢測modCount變量和expectedmodCount值是否相等,
若是相等就返回遍歷,不然拋出異常,終止遍歷.
舉例併發
//會拋出ConcurrentModificationException異常 for(Person person : Persons){ if(person.getId()==2) student.remove(person); }
注意
這裏異常的拋出條件時檢測到modCount = expectedmodCount 這個條件.this
若是集合發生變化時修改modCount值, 恰好有設置爲了expectedmodCount值, 則異常不會拋出.(好比刪除了數據,再添加一條數據)線程
//不會拋出ConcurrentModificationException異常 for(Person person : Persons){ if(person.getId()==2){ Persons.remove(person); Persons.add(new Person()); } }
因此不能依賴於這個異常是否拋出而進行併發操做的編程, 這個異常只建議檢測併發修改的bug.code
使用場景 :
java.util包下的集合類都是快速失敗機制的, 不能在多線程下發生併發修改(迭代過程當中被修改).對象
fail-safe ( 安全失敗 )
採用安全失敗機制的集合容器,在遍歷時不是直接在集合內容上訪問的,而是先copy原有集合內容,在拷貝的集合上進行遍歷.element
原理:
因爲迭代時是對原集合的拷貝的值進行遍歷,因此在遍歷過程當中對原集合所做的修改並不能被迭代器檢測到,因此不會出發ConcurrentModificationException
缺點:
基於拷貝內容的優勢是避免了ConcurrentModificationException,但一樣地, 迭代器並不能訪問到修改後的內容 (簡單來講就是, 迭代器遍歷的是開始遍歷那一刻拿到的集合拷貝,在遍歷期間原集合發生的修改迭代器是不知道的) 使用場景: java.util.concurrent包下的容器都是安全失敗的,能夠在多線程下併發使用,併發修改.