由ArrayList來深刻懂得Java中的fail-fast機制。本站提示廣大學習愛好者:(由ArrayList來深刻懂得Java中的fail-fast機制)文章只能為提供參考,不一定能成為您想要的結果。以下是由ArrayList來深刻懂得Java中的fail-fast機制正文
1. fail-fast簡介
“疾速掉敗”也就是fail-fast,它是Java聚集的一種毛病檢測機制。某個線程在對collection停止迭代時,不許可其他線程對該collection停止構造上的修正。
例如:假定存在兩個線程(線程1、線程2),線程1經由過程Iterator在遍歷聚集A中的元素,在某個時刻線程2修正了聚集A的構造(是構造下面的修正,而不是簡略的修正聚集元素的內容),那末這個時刻法式就會拋出 ConcurrentModificationException 異常,從而發生fail-fast。
迭代器的疾速掉劣行為沒法獲得包管,它不克不及包管必定會湧現該毛病,是以,ConcurrentModificationException應當僅用於檢測 bug。
Java.util包中的一切聚集類都是疾速掉敗的,而java.util.concurrent包中的聚集類都是平安掉敗的;
疾速掉敗的迭代器拋出ConcurrentModificationException,而平安掉敗的迭代器從不拋出這個異常。
2 fail-fast示例
示例代碼:(FastFailTest.java)
import java.util.*; import java.util.concurrent.*; /* * @desc java聚集中Fast-Fail的測試法式。 * * fast-fail事宜發生的前提:當多個線程對Collection停止操作時,若個中某一個線程經由過程iterator去遍歷聚集時,該聚集的內容被其他線程所轉變;則會拋出ConcurrentModificationException異常。 * fast-fail處理方法:經由過程util.concurrent聚集包下的響應類行止理,則不會發生fast-fail事宜。 * * 本例中,分離測試ArrayList和CopyOnWriteArrayList這兩種情形。ArrayList會發生fast-fail事宜,而CopyOnWriteArrayList不會發生fast-fail事宜。 * (01) 應用ArrayList時,會發生fast-fail事宜,拋出ConcurrentModificationException異常;界說以下: * private static List<String> list = new ArrayList<String>(); * (02) 應用時CopyOnWriteArrayList,不會發生fast-fail事宜;界說以下: * private static List<String> list = new CopyOnWriteArrayList<String>(); * * @author skywang */ public class FastFailTest { private static List<String> list = new ArrayList<String>(); //private static List<String> list = new CopyOnWriteArrayList<String>(); public static void main(String[] args) { // 同時啟動兩個線程對list停止操作! new ThreadOne().start(); new ThreadTwo().start(); } private static void printAll() { System.out.println(""); String value = null; Iterator iter = list.iterator(); while(iter.hasNext()) { value = (String)iter.next(); System.out.print(value+", "); } } /** * 向list中順次添加0,1,2,3,4,5,每添加一個數以後,就經由過程printAll()遍歷全部list */ private static class ThreadOne extends Thread { public void run() { int i = 0; while (i<6) { list.add(String.valueOf(i)); printAll(); i++; } } } /** * 向list中順次添加10,11,12,13,14,15,每添加一個數以後,就經由過程printAll()遍歷全部list */ private static class ThreadTwo extends Thread { public void run() { int i = 10; while (i<16) { list.add(String.valueOf(i)); printAll(); i++; } } } }
運轉成果
運轉該代碼,拋出異常java.util.ConcurrentModificationException!即,發生fail-fast事宜!
成果解釋
(01) FastFailTest中經由過程 new ThreadOne().start() 和 new ThreadTwo().start() 同時啟動兩個線程去操作list。
ThreadOne線程:向list中順次添加0,1,2,3,4,5。每添加一個數以後,就經由過程printAll()遍歷全部list。
ThreadTwo線程:向list中順次添加10,11,12,13,14,15。每添加一個數以後,就經由過程printAll()遍歷全部list。
(02) 當某一個線程遍歷list的進程中,list的內容被別的一個線程所轉變了;就會拋出ConcurrentModificationException異常,發生fail-fast事宜。
3. fail-fast處理方法
fail-fast機制,是一種毛病檢測機制。它只能被用來檢測毛病,由於JDK其實不包管fail-fast機制必定會產生。若在多線程情況下應用fail-fast機制的聚集,建議應用“java.util.concurrent包下的類”去代替“java.util包下的類”。
所以,本例中只須要將ArrayList調換成java.util.concurrent包下對應的類便可。 即,將代碼
private static List<String> list = new ArrayList<String>();
調換為
private static List<String> list = new CopyOnWriteArrayList<String>();
則可以處理該方法。
4. fail-fast道理
發生fail-fast事宜,是經由過程拋出ConcurrentModificationException異常來觸發的。
那末,ArrayList是若何拋出ConcurrentModificationException異常的呢?
我們曉得,ConcurrentModificationException是在操作Iterator時拋出的異常。我們先看看Iterator的源碼。ArrayList的Iterator是在父類AbstractList.java中完成的。代碼以下:
package java.util;
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> { ... // AbstractList中獨一的屬性 // 用來記載List修正的次數:每修正一次(添加/刪除等操作),將modCount+1 protected transient int modCount = 0; // 前往List對應迭代器。現實上,是前往Itr對象。 public Iterator<E> iterator() { return new Itr(); } // Itr是Iterator(迭代器)的完成類 private class Itr implements Iterator<E> { int cursor = 0; int lastRet = -1; // 修正數的記載值。 // 每次新建Itr()對象時,都邑保留新建該對象時對應的modCount; // 今後每次遍歷List中的元素的時刻,都邑比擬expectedModCount和modCount能否相等; // 若不相等,則拋出ConcurrentModificationException異常,發生fail-fast事宜。 int expectedModCount = modCount; public boolean hasNext() { return cursor != size(); } public E next() { // 獲得下一個元素之前,都邑斷定“新建Itr對象時保留的modCount”和“以後的modCount”能否相等; // 若不相等,則拋出ConcurrentModificationException異常,發生fail-fast事宜。 checkForComodification(); try { E next = get(cursor); lastRet = cursor++; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } public void remove() { if (lastRet == -1) throw new IllegalStateException(); checkForComodification(); try { AbstractList.this.remove(lastRet); if (lastRet < cursor) cursor--; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException e) { throw new ConcurrentModificationException(); } } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } } ... }
從中,我們可以發明在挪用 next() 和 remove()時,都邑履行 checkForComodification()。若 “modCount 不等於 expectedModCount”,則拋出ConcurrentModificationException異常,發生fail-fast事宜。
要弄明確 fail-fast機制,我們就要須要懂得甚麼時刻“modCount 不等於 expectedModCount”!
從Itr類中,我們曉得 expectedModCount 在創立Itr對象時,被賦值為 modCount。經由過程Itr,我們曉得:expectedModCount弗成能被修正為不等於 modCount。所以,須要考據的就是modCount什麼時候會被修正。
接上去,我們檢查ArrayList的源碼,來看看modCount是若何被修正的。
package java.util; public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { ... // list中容質變化時,對應的同步函數 public void ensureCapacity(int minCapacity) { modCount++; int oldCapacity = elementData.length; if (minCapacity > oldCapacity) { Object oldData[] = elementData; int newCapacity = (oldCapacity * 3)/2 + 1; if (newCapacity < minCapacity) newCapacity = minCapacity; // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } } // 添加元素到隊列最初 public boolean add(E e) { // 修正modCount ensureCapacity(size + 1); // Increments modCount!! elementData[size++] = e; return true; } // 添加元素到指定的地位 public void add(int index, E element) { if (index > size || index < 0) throw new IndexOutOfBoundsException( "Index: "+index+", Size: "+size); // 修正modCount ensureCapacity(size+1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; } // 添加聚集 public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; // 修正modCount ensureCapacity(size + numNew); // Increments modCount System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; } // 刪除指定地位的元素 public E remove(int index) { RangeCheck(index); // 修正modCount modCount++; E oldValue = (E) elementData[index]; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // Let gc do its work return oldValue; } // 疾速刪除指定地位的元素 private void fastRemove(int index) { // 修正modCount modCount++; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // Let gc do its work } // 清空聚集 public void clear() { // 修正modCount modCount++; // Let gc do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; } ... }
從中,我們發明:不管是add()、remove(),照樣clear(),只需觸及到修正聚集中的元素個數時,都邑轉變modCount的值。
接上去,我們再體系的梳理一下fail-fast是怎樣發生的。步調以下:
(01) 新建了一個ArrayList,稱號為arrayList。
(02) 向arrayList中添加內容。
(03) 新建一個“線程a”,並在“線程a”中經由過程Iterator重復的讀取arrayList的值。
(04) 新建一個“線程b”,在“線程b”中刪除arrayList中的一個“節點A”。
(05) 這時候,就會發生風趣的事宜了。
在某一時辰,“線程a”創立了arrayList的Iterator。此時“節點A”依然存在於arrayList中,創立arrayList時,expectedModCount = modCount(假定它們此時的值為N)。
在“線程a”在遍歷arrayList進程中的某一時辰,“線程b”履行了,而且“線程b”刪除arrayList中的“節點A”。“線程b”履行remove()停止刪除操作時,在remove()中履行了“modCount++”,此時modCount釀成了N+1!
“線程a”接著遍歷,當它履行到next()函數時,挪用checkForComodification()比擬“expectedModCount”和“modCount”的年夜小;而“expectedModCount=N”,“modCount=N+1”,如許,便拋出ConcurrentModificationException異常,發生fail-fast事宜。
至此,我們就完整懂得了fail-fast是若何發生的!
即,當多個線程對統一個聚集停止操作的時刻,某線程拜訪聚集的進程中,該聚集的內容被其他線程所轉變(即其它線程經由過程add、remove、clear等辦法,轉變了modCount的值);這時候,就會拋出ConcurrentModificationException異常,發生fail-fast事宜。
5. 處理fail-fast的道理
下面,解釋了“處理fail-fast機制的方法”,也曉得了“fail-fast發生的基本緣由”。接上去,我們再進一步談談java.util.concurrent包中是若何處理fail-fast事宜的。
照樣以和ArrayList對應的CopyOnWriteArrayList停止解釋。我們先看看CopyOnWriteArrayList的源碼:
package java.util.concurrent; import java.util.*; import java.util.concurrent.locks.*; import sun.misc.Unsafe; public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { ... // 前往聚集對應的迭代器 public Iterator<E> iterator() { return new聚集類中的fast-fail完成方法都差不多,我們以最簡略的ArrayList為例吧。protected transient int modCount = 0;記載的是我們對ArrayList修正的次數,好比我們挪用 add(),remove()等轉變數據的操作時,會將modCount++。protected transient int modCount = 0;記載的是我們對ArrayList修正的次數,好比我們挪用 add(),remove()等轉變數據的操作時,會將modCount++。 COWIterator<E>(getArray(), 0); } ... private static class COWIterator<E> implements ListIterator<E> { private final Object[] snapshot; private int cursor; private COWIterator(Object[] elements, int initialCursor) { cursor = initialCursor; // 新建COWIterator時,將聚集中的元素保留到一個新的拷貝數組中。 // 如許,當原始聚集的數據轉變,拷貝數據中的值也不會變更。 snapshot = elements; } public boolean hasNext() { return cursor < snapshot.length; } public boolean hasPrevious() { return cursor > 0; } public E next() { if (! hasNext()) throw new NoSuchElementException(); return (E) snapshot[cursor++]; } public E previous() { if (! hasPrevious()) throw new NoSuchElementException(); return (E) snapshot[--cursor]; } public int nextIndex() { return cursor; } public int previousIndex() { return cursor-1; } public void remove() { throw new UnsupportedOperationException(); } public void set(E e) { throw new UnsupportedOperationException(); } public void add(E e) { throw new UnsupportedOperationException(); } } ... }
從中,我們可以看出:
(01) 和ArrayList繼續於AbstractList分歧,CopyOnWriteArrayList沒有繼續於AbstractList,它僅僅只是完成了List接口。
(02) ArrayList的iterator()函數前往的Iterator是在AbstractList中完成的;而CopyOnWriteArrayList是本身完成Iterator。
(03) ArrayList的Iterator完成類中挪用next()時,會“挪用checkForComodification()比擬‘expectedModCount'和‘modCount'的年夜小”;然則,CopyOnWriteArrayList的Iterator完成類中,沒有所謂的checkForComodification(),更不會拋出ConcurrentModificationException異常!
6. 總結
因為HashMap(ArrayList)其實不是線程平安的,是以假如在應用迭代器的進程中有其他線程修正了map(這裡的修正是指構造上的修正,並不是指純真修正聚集內容的元素),那末將要拋出ConcurrentModificationException 即為fail-fast戰略
重要經由過程modCount域來完成,包管線程之間的可見性,modCount即為修正次數,關於HashMap(ArrayList)內容的修正就會增長這個值, 那末在迭代器的初始化進程中就會將這個值賦值給迭代器的expectedModCount
然則fail-fast行動其實不能包管,是以依附於此異常的法式的做法是毛病的