fail-fast 是什么
引用百度百科的數據:
fail-fast 機制是 java 集合 (Collection) 中的一種錯誤機制。當多個線程對同一個集合的內容進行操作時,就可能會產生 fail-fast 事件。例如:當某一個線程 A 通過 iterator 去遍歷某集合的過程中,若該集合的內容被其他線程所改變了;那么線程 A 訪問集合時,就會拋出 ConcurrentModificationException 異常,產生 fail-fast 事件。
多線程?并發修改?才會引起 fail-fast 機制保護程序?小 B 覺得這個答案沒有說全,面試官說了單線程也會引起 fail-fast 機制。那么關于單線程是否會引起 fail-fast 機制,百度百科說的對還是面試官說的對,寫一個 demo 就清楚了。
import java.util.ArrayList;
import java.util.List;
public class FastFailTest {
public static void main(String[] args) {
List< String > list = new ArrayList< String >();
list.add("張三");
list.add("李四");
list.add("王五");
list.add("趙六");
for(String s : list) {
if(s.equals("趙六")) {
list.remove(s);
System.out.println(list.toString());
}
}
}
}
從下圖的運行結果來看,list 已經完成了對 [趙六] 的 remove,說明并不是 remove 引發的問題,仔細查看異常原因:是在 ArrayList 的內部 Itr.checkForComodification() 方法出現的 ConcurrentModificationException 異常。小 B 感概了一句:網上資料不可盡信,動手實戰才能出真知。
原理
將異常定位到報錯的 ArrayList.java:911 行。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
可以看到這個方法 checkForComodification 對 modCount 和 expectedModCount 進行了比較,如果不相同就拋出異常。modCount 和 expectedModCount 分別又是什么呢?remove 方法中不是修改了 modCount 就是修改了 expectedModCount。
modCount 被定義在 ArrayList 的父類 AbstractList 中,每一次調用 Add、Remove、Clear 等方法 modCount 就被 +1,可以說明這個變量的作用就是記錄了 ArrayList 實際被修改的次數。
ArrayList 的 foreach 方法是用迭代器 Iterator 實現的,Iterator 在 ArrayList 中有一個實現類:Itr,它的成員變量 expectedModCount 在初始化的時候被賦值了 modCount。所以當 ArrayList 調用 remove 刪除元素時,modCount 被 +1,此時不等于 expectedModCount,在 foreach 試圖將局部變量 s 交接給下一個元素的時候,就出現了 ConcurrentModificationException 異常。
避免
經過分析,ConcurrentModificationException 是由于 modCount 和 expectedModCount 不一樣導致的。
那么如何避免在循環的時候 add、remove 元素不拋出異常呢?
for 循環
使用普通的 for 循環,這樣就可以不經過 Itr 內部類了。
List< String > list = new ArrayList< String >();
list.add("張三");
list.add("李四");
list.add("王五");
list.add("趙六");
for( int i = 0; i < list.size(); i++) {
String s = list.get(i);
if(s.equals("王五")) {
list.remove(s);
System.out.println(list.toString());
}
}
示例結果是 [張三, 李四, 趙六]
沒有出現異常。但是移除元素后面的索引已經被改變了。
迭代器 Iterator
直接使用迭代器 Iterator 中的方法,在它的remove 方法中顯示的將 expectedModCount 賦值成 modCount。
//Itr.remove()
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
定義一個迭代器局部變量,使用 hasNext() 方法控制 while 循環。
List< String > list = new ArrayList< String >();
list.add("張三");
list.add("李四");
list.add("王五");
list.add("趙六");
Iterator< String > iterator = list.iterator();
while (iterator.hasNext()) {
String s = iterator.next();
if(s.equals("王五")) {
iterator.remove();
System.out.println(list.toString());
}
}
CopyOnWriteArrayList
CopyOnWriteArrayList 是 java 并發包 java.util.concurrent 下面的類。它在操作 add、remove 元素時,先將原來的元素數組拷貝一份成為新的數組,在新數組上面做元素操作,修改完成后,將 CopyOnWriteArrayList 中數組的引用指向了新數組。
List< String > list = new CopyOnWriteArrayList< String >();
list.add("張三");
list.add("李四");
list.add("王五");
list.add("趙六");
for(String s : list) {
if(s.equals("李四")) {
list.remove(s);
System.out.println(list.toString());
}
}
總結
fail-fast 機制就是不允許程序員不管是在單線程還是多線程環境中遍歷集合的時候順便還操作集合里面的元素。
-
JAVA
+關注
關注
19文章
2966瀏覽量
104702 -
程序
+關注
關注
117文章
3785瀏覽量
81004 -
單線程
+關注
關注
0文章
17瀏覽量
1771
發布評論請先 登錄
相關推薦
評論