網頁

2018/12/15

Java java.util.ConcurrentModificationException 與fail-fast

如果對一個非執行緒安全的集合(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)的集合類別,例如ArrayListHashSet,及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();

參考:

沒有留言:

張貼留言