亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

解析Java的迭代器中的fast-fail錯(cuò)誤檢測(cè)機(jī)制

 更新時(shí)間:2016年02月19日 17:22:30   作者:楚云之南  
這篇文章主要介紹了Java的迭代器中的fast-fail錯(cuò)誤檢測(cè)機(jī)制,需要的朋友可以參考下

fail-fast 機(jī)制是java集合(Collection)中的一種錯(cuò)誤機(jī)制。當(dāng)多個(gè)線程對(duì)同一個(gè)集合的內(nèi)容進(jìn)行操作時(shí),就可能會(huì)產(chǎn)生fail-fast事件。例如:當(dāng)某一個(gè)線程A通過(guò)iterator去遍歷某集合的過(guò)程中,若該集合的內(nèi)容被其他線程所改變了;那么線程A訪問(wèn)集合時(shí),就會(huì)拋出ConcurrentModificationException異常,產(chǎn)生fail-fast事件。
fail-fast 機(jī)制是java集合(Collection)中的一種錯(cuò)誤機(jī)制。當(dāng)多個(gè)線程對(duì)同一個(gè)集合的內(nèi)容進(jìn)行操作時(shí),就可能會(huì)產(chǎn)生fail-fast事件。
例如:當(dāng)某一個(gè)線程A通過(guò)iterator去遍歷某集合的過(guò)程中,若該集合的內(nèi)容被其他線程所改變了;那么線程A訪問(wèn)集合時(shí),就會(huì)拋出ConcurrentModificationException異常,產(chǎn)生fail-fast事件。
要了解fail-fast機(jī)制,我們首先要對(duì)ConcurrentModificationException 異常有所了解。當(dāng)方法檢測(cè)到對(duì)象的并發(fā)修改,但不允許這種修改時(shí)就拋出該異常。同時(shí)需要注意的是,該異常不會(huì)始終指出對(duì)象已經(jīng)由不同線程并發(fā)修改,如果單線程違反了規(guī)則,同樣也有可能會(huì)拋出改異常。
誠(chéng)然,迭代器的快速失敗行為無(wú)法得到保證,它不能保證一定會(huì)出現(xiàn)該錯(cuò)誤,但是快速失敗操作會(huì)盡最大努力拋出ConcurrentModificationException異常,所以因此,為提高此類操作的正確性而編寫(xiě)一個(gè)依賴于此異常的程序是錯(cuò)誤的做法,正確做法是:ConcurrentModificationException 應(yīng)該僅用于檢測(cè) bug。

Java中的Iterator非常方便地為所有的數(shù)據(jù)源提供了一個(gè)統(tǒng)一的數(shù)據(jù)讀取(刪除)的接口,但是新手通常在使用的時(shí)候容易報(bào)如下錯(cuò)誤ConcurrentModificationException,原因是在使用迭代器時(shí)候底層數(shù)據(jù)被修改,最常見(jiàn)于數(shù)據(jù)源不是線程安全的類,如HashMap & ArrayList等。

為什么要有fast-fail
一個(gè)案例
來(lái)一個(gè)新手容易犯錯(cuò)的例子:

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");  
 }
}

更加常見(jiàn)的是在foreach(本質(zhì)一樣,都是調(diào)用Iterator時(shí),操作了原始的strings)語(yǔ)句中:

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

產(chǎn)生原因
Java中的集合類(數(shù)據(jù)源)分為兩種類型:線程安全,位于java.util.concurrent命名目錄下,如CopyOnWriteArrayList;線程不安全:位于java.util目錄下,如ArrayList,HashMap。所謂線程安全是在多線程環(huán)境下,這個(gè)類還能表現(xiàn)出和行為規(guī)范一致的結(jié)果,是否文縐縐的...自己google吧。

那既然我們可以有線程安全的集合替代品,那么為什么還要存在ArrayList等呢?因?yàn)榫€程安全的類通常需要通過(guò)各種手段去保持對(duì)數(shù)據(jù)訪問(wèn)的同步,所以通常來(lái)說(shuō)效率會(huì)比較差。而如果使用者清楚自身使用場(chǎng)景不存在并發(fā)的場(chǎng)景,那么使用非線程安全的集合類在速度上有很大的優(yōu)勢(shì)。

如果開(kāi)發(fā)者在使用時(shí)沒(méi)有注意,將非線程安全的集合類用在了并發(fā)的場(chǎng)景下,比如線程A獲取了ArrayList的iterator,然后線程B通過(guò)調(diào)用ArrayList.add()修改了ArrayList的數(shù)據(jù),此時(shí)就有可能會(huì)拋出ConcurrentModificationException,注意,這里是有可能。那為啥上面的例子里面也會(huì)報(bào)這個(gè)錯(cuò)誤呢?上面并不存在并發(fā)的情況,摟一眼源碼吧。

Iterator源碼分析
集合類中的fast-fail實(shí)現(xiàn)方式都差不多,我們以最簡(jiǎn)單的ArrayList為例吧。
ArrayList中會(huì)持有一個(gè)變量,聲明為:
protected transient int modCount = 0;記錄的是我們對(duì)ArrayList修改的次數(shù),比如我們調(diào)用 add(),remove()等改變數(shù)據(jù)的操作時(shí),會(huì)將modCount++。

我們通過(guò)ArrayList.iterator()返回的是一個(gè)實(shí)現(xiàn)了Iterator接口的ArrayListIterator:

private class ArrayListIterator implements Iterator<E> {

  //省略部分代碼.......
  //初始化時(shí),直接給expectedModCount賦ArrayList的修改次數(shù)
  private int expectedModCount = modCount;

  @SuppressWarnings("unchecked") public E next() {
      ............
    ArrayList<E> ourList = ArrayList.this;
    //簡(jiǎn)單比較一下當(dāng)前iterator初始化時(shí)ArrayList.modCount的值
    //和現(xiàn)在的值是否一致,如果不相等,認(rèn)為在獲取了當(dāng)前iterator之后
    //有別的位置(有可能是別的線程)修改了ArrayList,直接拋異常
    if (ourList.modCount != expectedModCount) {
      throw new ConcurrentModificationException();
    }
     ............
  }
}

原理很簡(jiǎn)單,構(gòu)建Iterator時(shí)將當(dāng)前ArrayList的modCount存起來(lái),以后每一次next()時(shí),判斷ArrayList的modCount值是否有變化,如果有,則是在這個(gè)過(guò)程中有代碼改變了數(shù)據(jù)(前面已經(jīng)提及,只有調(diào)用add() remove()等才會(huì)去修改modCount的值)。
這也說(shuō)明了為什么在例子里面我們并不是并發(fā)的場(chǎng)景也報(bào)錯(cuò),因?yàn)槲覀冋{(diào)用ArrayList.remove()時(shí)改變了modCount的值。

但是這個(gè)東西意義有多大呢?在我看來(lái)它有點(diǎn)畫(huà)蛇添足的嫌疑。因?yàn)樵谡嬲牟l(fā)場(chǎng)景下,這個(gè)fast-fail機(jī)制并不能真正即使發(fā)現(xiàn)另外線程訪問(wèn)并修改ArrayList中的數(shù)據(jù)。原因如下:

再看看modCount的定義protected transient int modCount = 0;。你沒(méi)有看錯(cuò),它就是一個(gè)普通的變量,那么在并發(fā)場(chǎng)景下由于共享對(duì)象的不可見(jiàn)性,有可能別的線程修改了ArrayList中的modCount,而iterator所在的線程卻并沒(méi)有讀取到這個(gè)更新。HashMap在1.6以前確實(shí)是用了volatile來(lái)修飾了modCount來(lái)保證各個(gè)線程直接對(duì)modCount的可見(jiàn)性,但是在1.7里面把這個(gè)修飾去掉了,而且認(rèn)為這是一個(gè)bug-->Java7去掉volatitle,可悲啊。。。原因嘛,就是JDK的開(kāi)發(fā)者認(rèn)為為了這么個(gè)破事而需要使用volatitle簡(jiǎn)直浪費(fèi)效率。

就算是使用volatitle就完事大吉了嗎?nono,舉個(gè)最簡(jiǎn)單的例子,線程A獲取了一個(gè)集合類的Iterator,線程B調(diào)用了集合類的add(),在add()還沒(méi)有執(zhí)行到modCount++時(shí),線程A獲取執(zhí)行,并執(zhí)行結(jié)束。在這種場(chǎng)景下,執(zhí)行結(jié)果并不確定。對(duì)于ArrayList的Iterator來(lái)說(shuō),有可能會(huì)報(bào)一個(gè)數(shù)組越界的異常...

總結(jié)
fast-fail是JDK為了提示開(kāi)發(fā)者將非線程安全的類使用到并發(fā)的場(chǎng)景下時(shí),拋出一個(gè)異常,及早發(fā)現(xiàn)代碼中的問(wèn)題。但正如本文前面所述,這種機(jī)制卻不能絕對(duì)正確地給出提示,而且老的JDK版本為了更好地支持這個(gè)機(jī)制還付出了一定的效率代價(jià)。

fast-fail存在的唯一價(jià)值可能就是給新手制造一些迷惑,給他深入探索的動(dòng)力...嘿嘿

補(bǔ)充:

很多網(wǎng)上資料說(shuō)在使用Iterator時(shí)是不能修改數(shù)據(jù)的,這樣也并不完全準(zhǔn)確。即便是支持fast-fail的Iterator本身也提供了remove()來(lái)刪除當(dāng)前遍歷到的元素,例如:ArrayListIterator中的remove(),前面舉的栗子改成如下即可:

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

相關(guān)文章

最新評(píng)論