平常在使用集合的時候肯定會遇到過遍歷刪除集合中的某些元素,哪麼在進行操作的時候大家有 沒有遇到過什麼問題呢?最近在使用集合進行遍歷刪除元素的時候,我就碰到了這個異常:java.util.ConcurrentModificationException。該異常是一個併發修改異常,於是引起我了的思考。
平常會使用遍歷刪除的幾種方式:
- 通過增強for刪除符合要求的元素(一個或者多個)
- 會出現異常java.util.ConcurrentModificationException
- 但假如加上break的話異常就不會出現,因為不會影響到後續的元素。
public void listRemove(){
List<Integer> list = new ArrayList<>();
for (Integer i:list){
list.remove();
}
}
************************************************
public void listRemove(){
List<Integer> list = new ArrayList<>();
for (Integer i:list){
list.remove();
break;
}
}
複製程式碼
- 通過普通for刪除符合要求元素
- 這裡雖然不會出現異常,但是你會發現會有些元素沒有被刪除掉的。原因是:比如我新增10個元素,然後把第三個刪除掉了。那麼原本下標為4的元素就會變成下標為3的元素了。如此類推就會出現資料不正確的情況。
public void listRemove(){
List<Student> students = new ArrayList<>();
student.add........ // 新增元素就略了
for (int i = 0; i<list.size();i++){
Student s = list.get(0);
list.remove(s);
}
}
複製程式碼
- 通過Iteator刪除符合要求的元素
- 假如這裡刪除用的是List的remove方法還是會照樣丟擲java.util.ConcurrentModificationException
- 如果不想丟擲異常又能正確刪除的話,建議使用Itreator的remove方法。
public void listRemove(){
List<Student> students = new ArrayList<>();
student.add........ // 新增元素就略了
Itreator<Student> it = students.Itreator();
while(it.hasNext()){
Student student = it.next();
if (student.getAge() == 10){
it.remove();
}
}
}
複製程式碼
那麼什麼會出現以上的情況呢?這就跟Fail-Fast機制有關了。
Fail-Fast機制是Java集合中的一種錯誤機制。當多個執行緒對同一個集合的內容進行操作時,就會發生Fail-Fast事件。
例如:當某一個執行緒A通過iterator去遍歷某集合的過程中,若該集合的內容被其他執行緒所改變了;那麼執行緒A訪問集合時,就會丟擲ConcurrentModificationException異常,產生fail-fast事件。
ConcurrentModificationException異常:當方法檢測到物件的併發修改,但不允許這種修改時就會丟擲這個異常。需要注意的是:如果單執行緒違反了規則,同樣也會丟擲這個異常
迭代器的快速失敗行為無法得到保證,它不能保證一定會出現該錯誤,但是快速失敗操作會盡最大努力丟擲ConcurrentModificationException異常,所以因此,為提高此類操作的正確性而編寫一個依賴於此異常的程式是錯誤的做法
結論:ConcurrentModificationException異常應該僅用於檢測BUG。
那麼為什麼Itreator修改就不會丟擲ConcurrentModificationException異常呢?
Itreator是工作在一個獨立的執行緒中,並有一個mutex鎖(互斥鎖)。
- synchronized修飾的同步程式碼塊就是一個互斥鎖。
執行緒在進入同步程式碼塊之前會自動獲取鎖,並且在退出同步程式碼塊時會自動釋放鎖,當某個執行緒請求一個由其他執行緒持有的鎖時,發出請求的執行緒就會阻塞。互斥鎖其實提供了一種原子操作,讓所有執行緒以序列的方式執行同步程式碼塊。
-
Iterator被建立之後會建立一個指向原來物件的單鏈索引表,當原來的物件數量發生變化時,這個索引表的內容不會同步改變,
-
所以當索引指標往後移動的時候就找不到要迭代的物件,所以按照 fail-fast 原則 Iterator會馬上丟擲java.util.ConcurrentModificationException 異常。
-
所以 Iterator 在工作的時候是不允許被迭代的物件被改變的。但你可以使用 Iterator 本身的方法 remove() 來刪除物件,Iterator.remove()方法會在刪除當前迭代物件的同時維護索引的一致性。
但如果你的Collection/Map物件實際只有一個或兩個元素的時候,ConcurrentModificationException異常並不會丟擲。
本文內容參考自:www.hollischuang.com/archives/33