如果對一個非執行緒安全的集合(Collection)進行多執行緒的操作時,當某一執行緒單獨修改了集合的結構,例如新增(add)或刪除(remove)一個元素,則會發生java.util.ConcurrentModificationException
例外錯誤。
ConcurrentModificationException
繼承RuntimeException
,是屬於不用用try catch
捕捉的例外,因該是在執行前就要解掉的錯誤。
例如下面範例便可能發生ConcurrentModificationException
。
// 建立一個ArrayList
List list = new ArrayList<>();
for(int i = 0; i < 1000; i++) {
list.add("item" + i);
}
// 執行緒1, 遍歷陣列印出每個元素值
Thread t1 = new Thread(()->{
for(String s : list) {
System.out.println(s);
}
});
// 執行緒2, 刪除陣列中的某個元素
Thread t2 = new Thread(()->{
list.remove(100);
});
t1.start();
t2.start();
在上面範例中,執行緒1對list
進行迭代,但在另一執行緒2中因試圖對同個list
刪除了一個元素,改動了list
的結構,所以導致ConcurrentModificationException
錯誤發生。
在Java集合中的非執行緒安全(non-synchronized)的集合類別,例如ArrayList
,HashSet
,及HashMap
所返回的迭代器(iterator)皆為fail-fast機制,其目的是為了防止一個集合被多個執行緒同步修改(concurrent modification)造成資料上的不正確。與其在之後因同步修改造成的資料上的不明確,寧願先拋出錯誤來避免,這樣的機制就稱為fail-fast。
下面引述Java API文件的說明:
...in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.
那必須使用多執行緒對集合操作該怎麼辦?怎麼避免多執行緒同步更動集合引發fail-fast機制所導致的ConcurrentModificationException
呢?以ArrayList
為例,那可以改用執行緒安全的CopyOnWriteArrayList
來操作。例如下面範例就不會發生錯誤。
// 建立一個CopyOnWriteArrayList
List list = new CopyOnWriteArrayList<>();
for(int i = 0; i < 1000; i++) {
list.add("item" + i);
}
// 執行緒1, 遍歷陣列印出每個元素值
Thread t1 = new Thread(()->{
for(String s : list) {
System.out.println(s);
}
});
// 執行緒2, 刪除陣列中的某個元素
Thread t2 = new Thread(()->{
list.remove(100);
});
t1.start();
t2.start();
參考:
沒有留言:
張貼留言