解析Java的迭代器中的fast-fail錯誤偵測機制_Android

來源:互聯網
上載者:User

fail-fast 機制是java集合(Collection)中的一種錯誤機制。當多個線程對同一個集合的內容進行操作時,就可能會產生fail-fast事件。例如:當某一個線程A通過iterator去遍曆某集合的過程中,若該集合的內容被其他線程所改變了;那麼線程A訪問集合時,就會拋出ConcurrentModificationException異常,產生fail-fast事件。
fail-fast 機制是java集合(Collection)中的一種錯誤機制。當多個線程對同一個集合的內容進行操作時,就可能會產生fail-fast事件。
例如:當某一個線程A通過iterator去遍曆某集合的過程中,若該集合的內容被其他線程所改變了;那麼線程A訪問集合時,就會拋出ConcurrentModificationException異常,產生fail-fast事件。
要瞭解fail-fast機制,我們首先要對ConcurrentModificationException 異常有所瞭解。當方法檢測到對象的並發修改,但不允許這種修改時就拋出該異常。同時需要注意的是,該異常不會始終指出對象已經由不同線程並發修改,如果單線程違反了規則,同樣也有可能會拋出改異常。
誠然,迭代器的快速失敗行為無法得到保證,它不能保證一定會出現該錯誤,但是快速失敗操作會盡最大努力拋出ConcurrentModificationException異常,所以因此,為提高此類操作的正確性而編寫一個依賴於此異常的程式是錯誤的做法,正確做法是:ConcurrentModificationException 應該僅用於檢測 bug。

Java中的Iterator非常方便地為所有的資料來源提供了一個統一的資料讀取(刪除)的介面,但是新手通常在使用的時候容易報如下錯誤ConcurrentModificationException,原因是在使用迭代器時候底層資料被修改,最常見於資料來源不是安全執行緒的類,如HashMap & ArrayList等。

為什麼要有fast-fail
一個案例
來一個新手容易犯錯的例子:

String[] stringArray = {"a","b","c","d"};List<String> strings = Arrays.asList(stringArray);Iterator<String> iterator = strings.iterator();while (iterator.hasNext()) {   if(iterator.next().equals("c")) {      strings.remove("c");   }}

更加常見的是在foreach(本質一樣,都是調用Iterator時,操作了原始的strings)語句中:

for(String s : strings) {   if(s.equals("c")) {      strings.remove("c"); }}

產生原因
Java中的集合類(資料來源)分為兩種類型:安全執行緒,位於java.util.concurrent命名目錄下,如CopyOnWriteArrayList;線程不安全:位於java.util目錄下,如ArrayList,HashMap。所謂安全執行緒是在多線程環境下,這個類還能表現出和行為規範一致的結果,是否文縐縐的...自己google吧。

那既然我們可以有安全執行緒的集合替代品,那麼為什麼還要存在ArrayList等呢?因為安全執行緒的類通常需要通過各種手段去保持對資料訪問的同步,所以通常來說效率會比較差。而如果使用者清楚自身使用情境不存在並發的情境,那麼使用非安全執行緒的集合類在速度上有很大的優勢。

如果開發人員在使用時沒有注意,將非安全執行緒的集合類用在了並發的情境下,比如線程A擷取了ArrayList的iterator,然後線程B通過調用ArrayList.add()修改了ArrayList的資料,此時就有可能會拋出ConcurrentModificationException,注意,這裡是有可能。那為啥上面的例子裡面也會報這個錯誤呢?上面並不存在並發的情況,摟一眼源碼吧。

Iterator源碼分析
集合類中的fast-fail實現方式都差不多,我們以最簡單的ArrayList為例吧。
ArrayList中會持有一個變數,聲明為:
protected transient int modCount = 0;記錄的是我們對ArrayList修改的次數,比如我們調用 add(),remove()等改變資料的操作時,會將modCount++。

我們通過ArrayList.iterator()返回的是一個實現了Iterator介面的ArrayListIterator:

private class ArrayListIterator implements Iterator<E> {  //省略部分代碼.......  //初始化時,直接給expectedModCount賦ArrayList的修改次數  private int expectedModCount = modCount;  @SuppressWarnings("unchecked") public E next() {      ............    ArrayList<E> ourList = ArrayList.this;    //簡單比較一下當前iterator初始化時ArrayList.modCount的值    //和現在的值是否一致,如果不相等,認為在擷取了當前iterator之後    //有別的位置(有可能是別的線程)修改了ArrayList,直接拋異常    if (ourList.modCount != expectedModCount) {      throw new ConcurrentModificationException();    }     ............  }}

原理很簡單,構建Iterator時將當前ArrayList的modCount存起來,以後每一次next()時,判斷ArrayList的modCount值是否有變化,如果有,則是在這個過程中有代碼改變了資料(前面已經提及,只有調用add() remove()等才會去修改modCount的值)。
這也說明了為什麼在例子裡面我們並不是並發的情境也報錯,因為我們調用ArrayList.remove()時改變了modCount的值。

但是這個東西意義有多大呢?在我看來它有點畫蛇添足的嫌疑。因為在真正的並發情境下,這個fast-fail機制並不能真正即使發現另外線程訪問並修改ArrayList中的資料。原因如下:

再看看modCount的定義protected transient int modCount = 0;。你沒有看錯,它就是一個普通的變數,那麼在並發情境下由於共用對象的不可見度,有可能別的線程修改了ArrayList中的modCount,而iterator所在的線程卻並沒有讀取到這個更新。HashMap在1.6以前確實是用了volatile來修飾了modCount來保證各個線程直接對modCount的可見度,但是在1.7裡面把這個修飾去掉了,而且認為這是一個bug-->Java7去掉volatitle,可悲啊。。。原因嘛,就是JDK的開發人員認為為了這麼個破事而需要使用volatitle簡直浪費效率。

就算是使用volatitle就完事大吉了嗎?nono,舉個最簡單的例子,線程A擷取了一個集合類的Iterator,線程B調用了集合類的add(),在add()還沒有執行到modCount++時,線程A擷取執行,並執行結束。在這種情境下,執行結果並不確定。對於ArrayList的Iterator來說,有可能會報一個數組越界的異常...

總結
fast-fail是JDK為了提示開發人員將非安全執行緒的類使用到並發的情境下時,拋出一個異常,及早發現代碼中的問題。但正如本文前面所述,這種機制卻不能絕對正確地給出提示,而且老的JDK版本為了更好地支援這個機制還付出了一定的效率代價。

fast-fail存在的唯一價值可能就是給新手製造一些迷惑,給他深入探索的動力...嘿嘿

補充:

很多網上資料說在使用Iterator時是不能修改資料的,這樣也並不完全準確。即便是支援fast-fail的Iterator本身也提供了remove()來刪除當前遍曆到的元素,例如:ArrayListIterator中的remove(),前面舉的栗子改成如下即可:

while (iterator.hasNext()) {   if(iterator.next().equals("c")) {      iterator.remove("c");   }}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.