Java中的fail-fast機(jī)制使用詳解
Java的fail-fast機(jī)制使用
fail-fast 機(jī)制是Java集合(Collection)中的一種錯誤機(jī)制。當(dāng)多個線程對同一個集合的內(nèi)容進(jìn)行操作時,就可能會產(chǎn)生fail-fast事件。
例如:
- 當(dāng)某一個線程 A 通過 iterator 去遍歷某集合的過程中,若該集合的內(nèi)容被其他線程所改變了,那么線程 A 訪問集合時,就會拋出 ConcurrentModificationException 異常,產(chǎn)生 fail-fast 事件。
- 這里的操作主要是指 add、remove 和 clear,對集合元素個數(shù)進(jìn)行修改。
舉例代碼
單線程,在foreach循環(huán)里對某些集合元素進(jìn)行元素的remove/add操作的時候,會觸發(fā)fail-fast機(jī)制
public static void main(String[] args){
List<String> strList = new ArrayList<>();
strList.add("AA");
strList.add("aa");
strList.add("BB");
strList.add("CC");
for(String str : strList){
if("aa".equals(str)){
strList.remove(str);
}
}
}多線程,在一個線程讀時,另一個線程寫入list,讀線程會fail-fast
// 測試
public class TreadDemo1 {
public static void main(String[] args){
List<String> strList = new ArrayList<>();
strList.add("AA");
strList.add("aa");
strList.add("BB");
strList.add("CC");
strList.add("DD");
new MyThread1(strList).start();
new MyThread2(strList).start();
}
static class Mythread1 extends Thread {
private List<String> list;
public Mythread1(List<String> list){
this.list = list;
}
@Override
public void run(){
for(String str : list){
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("MtThread1:"+str);
}
}
}
static class MyThread2 extends Thread {
private list<String> list;
public Mythread2(List<String> list){
this.list = list;
}
@Override
public void run(){
for(int i = 0; i < list.size(); i++){
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
if("aa".equals(list.get(i))){
list.remove(i);
}
}
System.out.println("MtThread2:"+list);
}
}
}原理
將單線程編譯后的.class反編譯后發(fā)現(xiàn),foreach其實是依賴while循環(huán)和Iterator實現(xiàn)的。
通過跟蹤代碼的異常堆棧,發(fā)現(xiàn)真正拋出異常的代碼是:java.util.ArrayList$Itr.checkForComodification();該方法實在iterator.next()方法中調(diào)用的:
final void checkForComodification(){
if(modCount != expectedModCount)
throw new ConcurrentModificationException();
}在該方法中modCount 和 expectedModCount進(jìn)行了比較,如果二者不相等,則拋出ConcurrentModificationException 異常。
modCount是ArrayList中的一個成員變量。表示該集合實際被修改的次數(shù)。(操作集合類的remove()、add()、clear()方法會改變這個變量值)expectedModCount是ArrayList 中的一個內(nèi)部類---Itr(Iterator接口)中的成員變量。表示這個迭代器預(yù)期該集合被修改的次數(shù)。其值隨著Itr被創(chuàng)建而初始化。只有通過迭代器對集合進(jìn)行操作,該值才會改變。
所以,在使用Java的集合類的時候,如果發(fā)生ConcurrentModificationException 異常,優(yōu)先考慮fail-fast有關(guān)的情況。
解決方式
1)使用普通for循環(huán)進(jìn)行操作
普通for循環(huán)沒有使用到Iterator的遍歷,所以不會進(jìn)行fail-fast的檢驗。
public static void main(String[] args){
List<String> strList = new ArrayList<>();
strList.add("AA");
strList.add("aa");
strList.add("BB");
strList.add("CC");
strList.add("DD");
for(int i = 0; i < strList.size(); i++){
if("aa".equals(strList.get(i))){
strList.remove(i);
}
}
}2)直接使用Iterator 進(jìn)行操作
public static void main(String[] args){
List<String> strList = new ArrayList<>();
strList.add("AA");
strList.add("aa");
strList.add("BB");
strList.add("CC");
strList.add("DD");
Iterator<String> iterator = strList.iterator();
while(iterator.hasNext()){
if("aa".equals(iterator.next())){
iterator.remove();
}
}
}3)使用Java 8中提供的filter 過濾
Java 8 中可以把集合轉(zhuǎn)換成流,對于流有一種filter操作,可以對原始Stream 進(jìn)行某項過濾,通過過濾的元素被留下了生成一個新的Stream。
public static void main(String[] args){
List<String> strList = new ArrayList<>();
strList.add("AA");
strList.add("aa");
strList.add("BB");
strList.add("CC");
strList.add("DD");
strList = strList.stream().filter(e -> !"aa".equals(e)).collect(Collectors.toList());
System.out.println(strList);
}4)使用fail-safe的集合類
為了避免觸發(fā)fail-fast機(jī)制導(dǎo)致異常,我們可以使用Java中提供的一些采用了fail-safe機(jī)制的集合類。
java.util.concurrent包下的容器都是fail-safe的,可以在多線程下并發(fā)使用,并發(fā)修改。同時也可以在foreach中進(jìn)行add/remove等操作。
5)也可以使用foreach循環(huán)
如果我們非常確定一個集合中,某個即將刪除的元素只包含一個的話,也是可以使用foreach循環(huán)的,只要刪除之后,立即結(jié)束循環(huán)體,不在繼續(xù)執(zhí)行遍歷就可以。
public static void main(String[] args){
List<String> strList = new ArrayList<>();
strList.add("AA");
strList.add("aa");
strList.add("BB");
strList.add("CC");
for(String str : strList){
if("aa".equals(str)){
strList.remove(str);
break;
}
}
System.out.println(strList);
}總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java實現(xiàn)調(diào)用對方http接口得到返回數(shù)據(jù)
這篇文章主要介紹了Java實現(xiàn)調(diào)用對方http接口得到返回數(shù)據(jù),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09
Spring Security SecurityContextHolder組件示例說明
SpringSecurity的SecurityContextHolder組件是存儲當(dāng)前安全上下文的地方,包括認(rèn)證用戶信息,它支持全局訪問、線程局部存儲和上下文傳播,是SpringSecurity認(rèn)證和授權(quán)的核心,文章通過示例展示了如何訪問已認(rèn)證用戶的詳細(xì)信息、手動設(shè)置認(rèn)證信息以及使用認(rèn)證信息保護(hù)方法2024-11-11
SpringBoot?AnnotationUtils工具類的使用實例詳解
這篇文章主要介紹了SpringBoot?AnnotationUtils工具類的使用,使用自定義注解標(biāo)記業(yè)務(wù)方法,原生Java獲取注解及AnnotationUtils工具類獲取方法,本文通過實例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-09-09
Java字符編碼轉(zhuǎn)換(從UTF-8到GBK)的實現(xiàn)原理與實踐
在計算機(jī)處理文本的過程中,字符編碼轉(zhuǎn)換是跨語言、跨平臺數(shù)據(jù)交互的核心環(huán)節(jié),本文詳細(xì)介紹了Java中Unicode與GBK字符集之間的映射原理及UTF-8與GBK編碼之間的轉(zhuǎn)換邏輯,幫助開發(fā)者理解底層機(jī)制并避免編碼問題,需要的朋友可以參考下2026-01-01
MybatisX-Generator不生成domain文件夾的問題及解決
在使用MybatisX-Generator生成數(shù)據(jù)庫表實體時,如果發(fā)現(xiàn)沒有生成domain文件夾以及User.java文件,是因為MybatisX版本更新,最新版需要在options里額外勾選model才能生成domain,勾選model并點擊finish后,成功生成domain文件夾及User.java文件2025-01-01
Java大數(shù)字運(yùn)算之BigInteger
在Java中提供了大數(shù)字的操作類,即 java.math.BigInteger 類與 java.math.BigDecimal 類。其中,BigInteger 類是針對大整數(shù)的處理類,這里有Integer 類的解釋,使用方法和實例,需要的朋友可以參考下。2017-08-08

