程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 由ArrayList來深刻懂得Java中的fail-fast機制

由ArrayList來深刻懂得Java中的fail-fast機制

編輯:關於JAVA

由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行動其實不能包管,是以依附於此異常的法式的做法是毛病的

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved