Java集合包中的fail fast機(jī)制詳解
Java的fail fast機(jī)制
首先說(shuō)說(shuō)現(xiàn)象,當(dāng)我們使用iterator迭代器遍歷一個(gè)集合的過(guò)程中,如果其它線(xiàn)程,或者它自己向這個(gè)集合新增或刪除了一個(gè)key-value,那么當(dāng)前線(xiàn)程就會(huì)拋出ConcurrentModificationException異常。
然后說(shuō)說(shuō)原理。無(wú)論是AbstractList,還是HashMap,它們都擁有一個(gè)modCount變量,每當(dāng)我們向集合新增或者刪除元素,都會(huì)使modCount的值增加1,這個(gè)參數(shù)就好像集合的操作版本號(hào)。當(dāng)我們?cè)诘?,?huì)創(chuàng)建一個(gè)expectedModCount,記錄了迭代之前的操作版本號(hào),每次迭代獲取數(shù)據(jù)時(shí),都會(huì)檢查一下,當(dāng)前的modCount是否與expectedModCount想等,若不相等,則說(shuō)明在當(dāng)前線(xiàn)程迭代遍歷元素時(shí),有其他的線(xiàn)程操作了集合。
集合包下面的集合都不是線(xiàn)程安全的,都會(huì)有并發(fā)問(wèn)題,所以都包含modCount變量。使用fail fast機(jī)制,當(dāng)一個(gè)線(xiàn)程在迭代遍歷,另一個(gè)線(xiàn)程在新增、刪除元素時(shí),就會(huì)立刻報(bào)錯(cuò)。
注意: 一個(gè)線(xiàn)程在迭代遍歷,另一個(gè)線(xiàn)程在修改元素時(shí),并不會(huì)觸發(fā)fail fast。可以看ArrayList和LinkedList的set()源碼,沒(méi)有發(fā)現(xiàn)modCount的痕跡(你只要不刪除,或者新增元素,就沒(méi)問(wèn)題)。
并且,fail fast僅僅是一個(gè)錯(cuò)誤檢測(cè)機(jī)制,因?yàn)镴DK并不能保證fail fast一定會(huì)發(fā)生。若在多線(xiàn)程環(huán)境下使用fail-fast機(jī)制的集合,建議使用“java.util.concurrent包下的類(lèi)”去取代“java.util包下的類(lèi)”。
口說(shuō)無(wú)憑,我們來(lái)看看源碼。就拿LinkedList來(lái)舉例,代碼如下所示:
LinkedList<String> list = new LinkedList<>(); list.add("123"); list.add("456"); list.add("789"); for (String str : list) { list.remove(); }
首次執(zhí)行l(wèi)ist.remove()是不會(huì)報(bào)錯(cuò)的,但是當(dāng)進(jìn)入下一輪循環(huán)時(shí),就會(huì)報(bào)錯(cuò)。
當(dāng)我們使用for循環(huán)遍歷任何一個(gè)List之前,底層都會(huì)調(diào)用listIterator( )方法,創(chuàng)建一個(gè)ListIterator對(duì)象,用于遍歷。LinkedList也不例外,它重寫(xiě)了listIterator()方法,返回了一個(gè)自定義的ListItr對(duì)象。
public ListIterator<E> listIterator(int index) { checkPositionIndex(index); return new ListItr(index); } private class ListItr implements ListIterator<E> { // 省略.. private int expectedModCount = modCount; ListItr(int index) { 省略.. } public E next() { checkForComodification(); 省略.. } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
我省略了許多與本文無(wú)關(guān)的代碼,關(guān)鍵代碼就那么幾句。執(zhí)行l(wèi)istIerator()方法時(shí),一定會(huì)創(chuàng)建一個(gè)ListItr對(duì)象,同時(shí)一定會(huì)初始化它的成員變量expectedModCount,等于當(dāng)前的modCount。
接下來(lái)就是遍歷,遍歷無(wú)非就是獲取List中的下一個(gè)元素,用屁股想想,調(diào)用的不就是next()方法么,next()方法一上來(lái)就執(zhí)行了checkForComodification(),比較一下當(dāng)前的modCount是否與expectedModCount一樣,如果不一樣,就拋出異常。
這個(gè)意思不就是在說(shuō),如果在遍歷List的過(guò)程中,你敢做新增或者刪除操作(累加了modCount的值),那就會(huì)報(bào)錯(cuò)。
值得注意的是,報(bào)錯(cuò)的地方不是list.remove(),報(bào)錯(cuò)的是你做了新增、刪除操作后的接下來(lái)的一輪遍歷時(shí),獲取數(shù)據(jù)時(shí)會(huì)報(bào)錯(cuò)。
那是不是只要遍歷集合時(shí),新增或者刪除(或者其它改變modCount的操作),都會(huì)報(bào)錯(cuò)呢?
不是的,只要你在查詢(xún)數(shù)據(jù)的時(shí)候,繞開(kāi)包含了modCount檢查的方法,就不會(huì)報(bào)錯(cuò)。
String str; while((str = list.peekFirst()) != null) { System.out.println(str); list.pop(); }
上面的代碼也是在遍歷鏈表,但是不會(huì)報(bào)錯(cuò),因?yàn)閘ist.pop()沒(méi)有修改modCount的值,peekFirst()也沒(méi)有去檢查modCount,fail fast不復(fù)存在,就不會(huì)報(bào)錯(cuò)。
到此這篇關(guān)于Java集合包中的fail fast機(jī)制詳解的文章就介紹到這了,更多相關(guān)Java的fail fast機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mybatis自定義攔截器實(shí)現(xiàn)權(quán)限功能
本文主要介紹了Mybatis自定義攔截器實(shí)現(xiàn)權(quán)限功能,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-12-12Mybatis控制臺(tái)打印Sql語(yǔ)句的實(shí)現(xiàn)代碼
MyBatis是一個(gè)支持普通SQL查詢(xún),存儲(chǔ)過(guò)程和高級(jí)映射的優(yōu)秀持久層框架,下面給大家介紹Mybatis控制臺(tái)打印Sql語(yǔ)句的實(shí)現(xiàn)代碼,非常不錯(cuò),感興趣的朋友一起看下吧2016-07-07Java如何通過(guò)線(xiàn)程解決生產(chǎn)者/消費(fèi)者問(wèn)題
這篇文章主要介紹了Java如何通過(guò)線(xiàn)程解決生產(chǎn)者/消費(fèi)者問(wèn)題,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下2020-10-10從log4j切換到logback后項(xiàng)目無(wú)法啟動(dòng)的問(wèn)題及解決方法
這篇文章主要介紹了從log4j切換到logback后項(xiàng)目無(wú)法啟動(dòng)的問(wèn)題及解決方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-01-01springboot如何配置嵌套map和list參數(shù)
這篇文章主要介紹了springboot如何配置嵌套map和list參數(shù)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-03-03