您好,登錄后才能下訂單哦!
這篇文章主要介紹了java中fail-fast示例,具有一定借鑒價值,需要的朋友可以參考下。希望大家閱讀完這篇文章后大有收獲。下面讓小編帶著大家一起了解一下。
在JDK的Collection中我們時常會看到類似于這樣的話:
例如,ArrayList:
注意,迭代器的快速失敗行為無法得到保證,因為一般來說,不可能對是否出現不同步并發修改做出任何硬性保證。快速失敗迭代器會盡 最大努力拋出 ConcurrentModificationException。因此,為提高這類迭代器的正確性而編寫一個依賴于此異常的程序是錯誤的做法:迭 代器的快速失敗行為應該僅用于檢測 bug。
HashMap中:
注意,迭代器的快速失敗行為不能得到保證,一般來說,存在非同步的并發修改時,不可能作出任何堅決的保證。快速失敗迭代器盡最大 努力拋出 ConcurrentModificationException。因此,編寫依賴于此異常的程序的做法是錯誤的,正確做法是:迭代器的快速失敗行為應 該僅用于檢測程序錯誤。
在這兩段話中反復地提到”快速失敗”。那么何為”快速失敗”機制呢?
“快速失敗”也就是fail-fast,它是Java集合的一種錯誤檢測機制。當多個線程對集合進行結構上的改變的操作時,有可能會產生fail-fast機制。記住是有可能,而不是一定。例如:假設存在兩個線程(線程1、線程2),線程1通過Iterator在遍歷集合A中的元素,在某個時候線程2修改了集合A的結構(是結構上面的修改,而不是簡單的修改集合元素的內容),那么這個時候程序就會拋出 ConcurrentModificationException 異常,從而產生fail-fast機制。
一、fail-fast示例
public class FailFastTest { private static List<Integer> list = new ArrayList<>(); /** * @desc:線程one迭代list * @Project:test * @file:FailFastTest.java * @Authro:chenssy * @data:2014年7月26日 */ private static class threadOne extends Thread{ public void run() { Iterator<Integer> iterator = list.iterator(); while(iterator.hasNext()){ int i = iterator.next(); System.out.println("ThreadOne 遍歷:" + i); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } } /** * @desc:當i == 3時,修改list * @Project:test * @file:FailFastTest.java * @Authro:chenssy * @data:2014年7月26日 */ private static class threadTwo extends Thread{ public void run(){ int i = 0 ; while(i < 6){ System.out.println("ThreadTwo run:" + i); if(i == 3){ list.remove(i); } i++; } } } public static void main(String[] args) { for(int i = 0 ; i < 10;i++){ list.add(i); } new threadOne().start(); new threadTwo().start(); } }
運行結果:
ThreadOne 遍歷:0 ThreadTwo run:0 ThreadTwo run:1 ThreadTwo run:2 ThreadTwo run:3 ThreadTwo run:4 ThreadTwo run:5 Exception in thread "Thread-0" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(Unknown Source) at java.util.ArrayList$Itr.next(Unknown Source) at test.ArrayListTest$threadOne.run(ArrayListTest.java:23
二、fail-fast產生原因
通過上面的示例和講解,我初步知道fail-fast產生的原因就在于程序在對 collection 進行迭代時,某個線程對該 collection 在結構上對其做了修改,這時迭代器就會拋出 ConcurrentModificationException 異常信息,從而產生 fail-fast。
要了解fail-fast機制,我們首先要對ConcurrentModificationException 異常有所了解。當方法檢測到對象的并發修改,但不允許這種修改時就拋出該異常。同時需要注意的是,該異常不會始終指出對象已經由不同線程并發修改,如果單線程違反了規則,同樣也有可能會拋出改異常。
誠然,迭代器的快速失敗行為無法得到保證,它不能保證一定會出現該錯誤,但是快速失敗操作會盡最大努力拋出ConcurrentModificationException異常,所以因此,為提高此類操作的正確性而編寫一個依賴于此異常的程序是錯誤的做法,正確做法是:ConcurrentModificationException 應該僅用于檢測 bug。下面我將以ArrayList為例進一步分析fail-fast產生的原因。
從前面我們知道fail-fast是在操作迭代器時產生的。現在我們來看看ArrayList中迭代器的源代碼:
private class Itr implements Iterator<E> { int cursor; int lastRet = -1; int expectedModCount = ArrayList.this.modCount; public boolean hasNext() { return (this.cursor != ArrayList.this.size); } public E next() { checkForComodification(); /** 省略此處代碼 */ } public void remove() { if (this.lastRet < 0) throw new IllegalStateException(); checkForComodification(); /** 省略此處代碼 */ } final void checkForComodification() { if (ArrayList.this.modCount == this.expectedModCount) return; throw new ConcurrentModificationException(); } }
從上面的源代碼我們可以看出,迭代器在調用next()、remove()方法時都是調用checkForComodification()方法,該方法主要就是檢測modCount == expectedModCount ? 若不等則拋出ConcurrentModificationException 異常,從而產生fail-fast機制。所以要弄清楚為什么會產生fail-fast機制我們就必須要用弄明白為什么modCount != expectedModCount ,他們的值在什么時候發生改變的。
expectedModCount 是在Itr中定義的:int expectedModCount = ArrayList.this.modCount;所以他的值是不可能會修改的,所以會變的就是modCount。modCount是在 AbstractList 中定義的,為全局變量:
protected transient int modCount = 0;
那么他什么時候因為什么原因而發生改變呢?請看ArrayList的源碼:
public boolean add(E paramE) { ensureCapacityInternal(this.size + 1); /** 省略此處代碼 */ } private void ensureCapacityInternal(int paramInt) { if (this.elementData == EMPTY_ELEMENTDATA) paramInt = Math.max(10, paramInt); ensureExplicitCapacity(paramInt); } private void ensureExplicitCapacity(int paramInt) { this.modCount += 1; //修改modCount /** 省略此處代碼 */ } public boolean remove(Object paramObject) { int i; if (paramObject == null) for (i = 0; i < this.size; ++i) { if (this.elementData[i] != null) continue; fastRemove(i); return true; } else for (i = 0; i < this.size; ++i) { if (!(paramObject.equals(this.elementData[i]))) continue; fastRemove(i); return true; } return false; } private void fastRemove(int paramInt) { this.modCount += 1; //修改modCount /** 省略此處代碼 */ } public void clear() { this.modCount += 1; //修改modCount /** 省略此處代碼 */ }
從上面的源代碼我們可以看出,ArrayList中無論add、remove、clear方法只要是涉及了改變ArrayList元素的個數的方法都會導致modCount的改變。所以我們這里可以初步判斷由于expectedModCount 得值與modCount的改變不同步,導致兩者之間不等從而產生fail-fast機制。知道產生fail-fast產生的根本原因了,我們可以有如下場景:
有兩個線程(線程A,線程B),其中線程A負責遍歷list、線程B修改list。線程A在遍歷list過程的某個時候(此時expectedModCount = modCount=N),線程啟動,同時線程B增加一個元素,這是modCount的值發生改變(modCount + 1 = N + 1)。線程A繼續遍歷執行next方法時,通告checkForComodification方法發現expectedModCount = N ,而modCount = N + 1,兩者不等,這時就拋出ConcurrentModificationException 異常,從而產生fail-fast機制。
所以,直到這里我們已經完全了解了fail-fast產生的根本原因了。知道了原因就好找解決辦法了。
三、fail-fast解決辦法
通過前面的實例、源碼分析,我想各位已經基本了解了fail-fast的機制,下面我就產生的原因提出解決方案。這里有兩種解決方案:
方案一: 在遍歷過程中所有涉及到改變modCount值得地方全部加上synchronized或者直接使用Collections.synchronizedList,這樣就可以解決。但是不推薦,因為增刪造成的同步鎖可能會阻塞遍歷操作。
方案二: 使用CopyOnWriteArrayList來替換ArrayList。推薦使用該方案。
以上就是fail-fast機制的詳細內容,更多請關注億速云其它相關文章!
感謝你能夠認真閱讀完這篇文章,希望小編分享java中fail-fast示例內容對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,遇到問題就找億速云,詳細的解決方法等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。