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