fail-fast(快速失败/报错机制)-ConcurrentModificationException
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
一、fail-fast機(jī)制(快速報(bào)錯(cuò)機(jī)制)
這是《Java編程思想》中關(guān)于快速報(bào)錯(cuò)機(jī)制的描述
Java容器有一種保護(hù)機(jī)制,能夠防止多個(gè)進(jìn)程同時(shí)修改同一個(gè)容器的內(nèi)容。如果在你迭代遍歷容器的過程中,另一個(gè)進(jìn)程介入其中,并且插入、刪除或者修改此容器內(nèi)的某個(gè)對象,那么就會(huì)出現(xiàn)問題:也許迭代過程中已經(jīng)處理過容器中的該元素了,也許還沒處理,也許在調(diào)用size()之后容器的尺寸收縮了——還有許多災(zāi)難情景。Java容器類類庫采用快速報(bào)錯(cuò)(fail-fast)機(jī)制。它會(huì)探查容器上的任何除了你的進(jìn)程所進(jìn)行的操作以外的所有變化,一旦它發(fā)現(xiàn)其它進(jìn)程修改了容器,就會(huì)立刻拋出ConcurrentModificationException異常。這就是“快速報(bào)錯(cuò)”的意思——即,不是使用復(fù)雜的算法在事后來檢查問題。——from《Java編程思想》p517
下面以一個(gè)demo開始,來理解快速報(bào)錯(cuò)機(jī)制。
二、遍歷容器的幾種方式
程序功能:分別使用for,foreach,iterator來遍歷(迭代)容器,然后刪除其中的值為”傻強(qiáng)”這個(gè)元素。
public class TestTest {private List<String> list;/** * 初始化操作 */@Beforepublic void setUp(){list = new ArrayList<String>();list.add("劉德華"); list.add("周潤發(fā)");list.add("傻強(qiáng)");list.add("古天樂");list.add("劉青云");System.out.println(list);}/** * Demo1:使用for循環(huán)刪除元素 */@Testpublic void testFor(){ for(int i=0;i<list.size();i++){//刪除傻強(qiáng)if("傻強(qiáng)".equals(list.get(i))){list.remove(i);}}System.out.println(list);}/** * Demo2:使用foreach刪除元素【錯(cuò)誤】 */@Testpublic void testForeach(){ for (String s : list) {//刪除傻強(qiáng)if("傻強(qiáng)".equals(s)){list.remove(s);}}System.out.println(list);}/** * Demo3:使用Iterator和Iterator的remove()刪除元素 */@Testpublic void testIterator(){Iterator<String> iterator = list.iterator();while(iterator.hasNext()){String s= iterator.next();//刪除lisiif("傻強(qiáng)".equals(s)){iterator.remove();//使用迭代器的remove()}}System.out.println(list);}/** * Demo4:使用Iterator和集合的remove刪除元素【錯(cuò)誤】 */@Testpublic void testIterator2(){Iterator<String> iterator = list.iterator();while(iterator.hasNext()){String s= iterator.next();//刪除傻強(qiáng)if("傻強(qiáng)".equals(s)){list.remove(s);//使用集合的remove}}System.out.println(list);}/** * Demo5:獲得iterator后進(jìn)行了錯(cuò)誤操作【錯(cuò)誤】 */@Testpublic void testIterator3(){Iterator<String> iterator = list.iterator();//錯(cuò)誤操作list.add("這是錯(cuò)誤的行為");while(iterator.hasNext()){String s= iterator.next();//刪除傻強(qiáng)if("傻強(qiáng)".equals(s)){iterator.remove();}}System.out.println(list);} }結(jié)果:只有Demo1和Demo3正確。其它demo都會(huì)報(bào)ConcurrentModificationException異常。這里就用到了fail-fast機(jī)制。
三、ArrayList中Iterator源碼分析
接下來開始分析。我們先看看ArrayLis中的關(guān)于迭代器的代碼
public Iterator<E> iterator() {return new Itr();}/** * An optimized version of AbstractList.Itr * * 覆蓋了父類中AbstractList.Itr的實(shí)現(xiàn)(優(yōu)化版) */private class Itr implements Iterator<E> {//下一個(gè)要返回元素的索引int cursor; // index of next element to return//最后一個(gè)要返回元素的索引,-1表示不存在int lastRet = -1; // index of last element returned; -1 if no such//記錄期望的修改次數(shù)(用于保證迭代器在遍歷過程中不會(huì)有對集合的修改操作(迭代器的自身的remove方法除外))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();}}/** * 檢查修改次數(shù) */final void checkForComodification() {//實(shí)際的修改次數(shù)和期望的修改次數(shù)不匹配,則拋出并發(fā)修改異常if (modCount != expectedModCount)throw new ConcurrentModificationException();}}原來,ArrayList從其父類AbstractList繼承了一個(gè)modCount屬性,每當(dāng)對ArrayList進(jìn)行修改(add,remove,clear等)時(shí),就會(huì)相應(yīng)的增加modCount的值。
而ArrayList中迭代器的實(shí)現(xiàn)類Itr也有一個(gè)expectedModCount屬性,一旦使用迭代器遍歷容器時(shí),就要調(diào)用iterator()方法,Itr類也就被初始化,expectedModCount就會(huì)被賦予一個(gè)與modCount相等的值。
接下來在遍歷過程中,每次調(diào)用next()方法獲取值時(shí)都會(huì)檢查modCount和expectedModCount兩個(gè)值是否相等(checkForComodification)。如果在遍歷過程中出現(xiàn)了對集合的其它修改操作,從而造成兩者不等,就會(huì)拋出ConcurrentModificationException。這不就是樂觀鎖的實(shí)現(xiàn)思想嗎。
另外,我們注意到迭代器自身的remove方法并不會(huì)修改modCount的值,這是因?yàn)槲覀兺ǔR矔?huì)通過迭代遍歷去刪除某一個(gè)指定的元素,所以迭代器中自身提供了該remove方法,并保證該remove方法是安全的,而不希望我們在迭代時(shí)使用容器提供remove方法。
四、避免fail-fast
要說明兩點(diǎn)
1.雖然ConcurrentModificationException被譯為并發(fā)修改異常,但這里的”并發(fā)”,并非僅僅指的是多線程場景,前面的例子很顯然是單線程場景。
在單線程情況下
要確保Iterator遍歷過程順利完成,必須保證遍歷過程中不更改集合的內(nèi)容(Iterator的remove()方法除外)。
多線程情況下
如果要在多線程環(huán)境中,在迭代ArrayList的同時(shí)也要修改ArrayList,則可以使用
Collections.synchronizedList(List list)或者CopyOnWriteArrayList。
其中CopyOnWriteArrayList是可以避免ConcurrentModificationException。
實(shí)際上CopyOnWriteArrayList、ConcurrentHashMap和CopyOnWriteArraySet都使用了可以避免ConcurrentModificationException的技術(shù)。
2.迭代器的快速失敗行為無法得到保證,它不能保證一定會(huì)出現(xiàn)該錯(cuò)誤,但是快速失敗操作會(huì)盡最大努力拋出ConcurrentModificationException異常。因此,為提高此類操作的正確性,我們不能依賴于此異常,而要使用上一條中提到的線程安全的容器。
五、CopyOnWriteArrayList不使用fail-fast機(jī)制
通過上面的分析,我么知道了ArrayList一邊使用迭代器遍歷一邊修改是會(huì)發(fā)生ConcurrentModificationException。但是,ArrayList對應(yīng)的線程安全容器CopyOnWriteArrayList卻不會(huì)發(fā)生ConcurrentModificationException。那是為什么呢?先來看源碼。
/** * 返回迭代器 * * 返回的迭代器提供了該迭代器被創(chuàng)建時(shí)列表的快照。 * 當(dāng)移動(dòng)迭代器時(shí),不需要同步。 * 迭代器不支持remove方法 */public Iterator<E> iterator() {return new COWIterator<E>(getArray(), 0);}/** * 內(nèi)部迭代器的實(shí)現(xiàn)類 */private static class COWIterator<E> implements ListIterator<E> {/**數(shù)組的快照*/private final Object[] snapshot;/** Index of element to be returned by subsequent call to next. */private int cursor;/** * 私有的構(gòu)造器 */private COWIterator(Object[] elements, int initialCursor) {cursor = initialCursor;//快照snapshot = elements;}public boolean hasNext() {return cursor < snapshot.length;}public boolean hasPrevious() {return cursor > 0;}@SuppressWarnings("unchecked")public E next() {if (! hasNext())throw new NoSuchElementException();return (E) snapshot[cursor++];}@SuppressWarnings("unchecked")public E previous() {if (! hasPrevious())throw new NoSuchElementException();return (E) snapshot[--cursor];}public int nextIndex() {return cursor;}public int previousIndex() {return cursor-1;}/** * Not supported. Always throws UnsupportedOperationException. * remove is not supported by this iterator. * 不支持。總是拋出不支持的操作異常 * 迭代器不支持remove方法。 */public void remove() {throw new UnsupportedOperationException();}/** * Not supported. Always throws UnsupportedOperationException. * set is not supported by this iterator. * 不支持。總是拋出不支持的操作異常 * 迭代器不支持set方法。 * */public void set(E e) {throw new UnsupportedOperationException();}/** * Not supported. Always throws UnsupportedOperationException. * add is not supported by this iterator. * 不支持。總是拋出不支持的操作異常 * 迭代器不支持aa方法。 * */public void add(E e) {throw new UnsupportedOperationException();}}原來,CopyOnWriteArrayList中的迭代器在創(chuàng)建之初,會(huì)保存一份對原數(shù)組的快照,之后所有迭代器的操作都是在快照數(shù)組上進(jìn)行的,原數(shù)組一點(diǎn)影響都沒有。同時(shí),即使是對快照數(shù)組進(jìn)行操作,也根本不支持迭代器上的修改(add,set,remove)操作。因此,根本就不會(huì)發(fā)生ConcurrentModificationException。
雖然在迭代時(shí)不支持迭代器上的修改操作,但是仍然可以直接使用容器的修改方法,這點(diǎn)恰好跟ArrayList相反。這也很容易解釋,因?yàn)榈鞑僮鞯氖强煺諗?shù)組,在原容器上進(jìn)行修改也是會(huì)創(chuàng)建一個(gè)新的數(shù)組,因此兩者根本不會(huì)干擾。
public class Demo {public static void main(String[] args) {CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();//添加元素0-4for(int i=0;i<5;i++){list.add(i+"");}System.out.println(list);//[0, 1, 2, 3, 4]//進(jìn)行迭代Iterator iterator = list.iterator();while(iterator.hasNext()){String num = (String) iterator.next();//迭代時(shí),刪除3if("3".equals(num)){//iterator.remove();//iterator不支持修改方法(add,set,remove)list.remove(num);//使用原容器的remove方法};}System.out.println(list);//[0, 1, 2, 4]} }轉(zhuǎn)載于:https://my.oschina.net/javandroid/blog/878235
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的fail-fast(快速失败/报错机制)-ConcurrentModificationException的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: struts2的DevMode(开发模式
- 下一篇: Spring MVC 环境搭建(一)