Java集合包中的fail fast機(jī)制詳解
Java的fail fast機(jī)制
首先說(shuō)說(shuō)現(xiàn)象,當(dāng)我們使用iterator迭代器遍歷一個(gè)集合的過(guò)程中,如果其它線程,或者它自己向這個(gè)集合新增或刪除了一個(gè)key-value,那么當(dāng)前線程就會(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)前線程迭代遍歷元素時(shí),有其他的線程操作了集合。
集合包下面的集合都不是線程安全的,都會(huì)有并發(fā)問(wèn)題,所以都包含modCount變量。使用fail fast機(jī)制,當(dāng)一個(gè)線程在迭代遍歷,另一個(gè)線程在新增、刪除元素時(shí),就會(huì)立刻報(bào)錯(cuò)。
注意: 一個(gè)線程在迭代遍歷,另一個(gè)線程在修改元素時(shí),并不會(huì)觸發(fā)fail fast??梢钥碅rrayList和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ā)生。若在多線程環(huán)境下使用fail-fast機(jī)制的集合,建議使用“java.util.concurrent包下的類”去取代“java.util包下的類”。
口說(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ò)呢?
不是的,只要你在查詢數(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)文章
SpringBoot 普通類調(diào)用Bean對(duì)象的一種方式推薦
這篇文章主要介紹了SpringBoot 普通類調(diào)用Bean對(duì)象的一種方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
關(guān)于synchronized的參數(shù)及其含義
這篇文章主要介紹了synchronized的參數(shù)及其含義詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
java題解Leetcode 8字符串轉(zhuǎn)換整數(shù)
這篇文章主要為大家介紹了java題解Leetcode 8字符串轉(zhuǎn)換整數(shù)實(shí)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
SpringBoot通過(guò)注解下載任意對(duì)象
下載功能應(yīng)該是比較常見(jiàn)的功能了,雖然一個(gè)項(xiàng)目里面可能出現(xiàn)的不多,但是基本上每個(gè)項(xiàng)目都會(huì)有,而且有些下載功能其實(shí)還是比較繁雜的,這篇文章主要介紹了SpringBoot一個(gè)注解就能幫你下載任意對(duì)象,需要的朋友可以參考下2023-08-08
解決springdataJPA對(duì)原生sql支持的問(wèn)題
這篇文章主要介紹了解決springdataJPA對(duì)原生sql支持的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
Java排序算法之歸并排序簡(jiǎn)單實(shí)現(xiàn)
這篇文章主要介紹了Java排序算法之歸并排序簡(jiǎn)單實(shí)現(xiàn),具有一定借鑒價(jià)值,需要的朋友可以參考下。2017-12-12

