今天再羣裏看到大牛們討論,併發環境下對集合元素作remove操做的注意事項,順帶也學習一下:java
一、不要在foreach循環裏進行元素的remove/add操做,remove元素請使用iterator方式,如併發操做,還須要對iterator對象加鎖數組
反例:安全
List<String> strs = new ArrayList<String>(); strs.add("1"); strs.add("2"); for (String str : strs) { if("1".equals(str)){ strs.remove(str); } }
這個反例,在併發執行的時候,不必定會出問題,但不表明必定是安全的。併發
正確的作法是:學習
Iterator<String> it = strs.iterator(); while(it.hasNext()){ String temp = it.next(); if(刪除元素的條件){ it.remove()); } }
緣由是:java自帶的一種迭代器快速失敗機制。this
---------------------------------------------------------------------------------------------------spa
迭代器的快速失敗行爲沒法獲得保證,由於通常來講,不可能對是否出現不一樣步併發修改作出任何硬性保證。快速失敗迭代器會盡最大努力拋出 ConcurrentModificationException,爲提升這類迭代器的正確性而編寫一個依賴於此異常的程序是錯誤的作法:迭代器的快速失敗行爲應該僅用於檢測 bug。線程
它是Java集合的一種錯誤檢測機制。當多個線程對集合進行結構上的改變的操做時,有可能會產生fail-fast機制。記住是有可能,而不是必定。code
ConcurrentModificationException不會始終指出對象已經由不一樣線程併發修改,若是單線程違反了規則,一樣也有可能會拋出該異常。對象
迭代器在調用next()、remove()方法時都是調用checkForComodification()方法,該方法主要就是檢測modCount == expectedModCount ? 若不等則拋出ConcurrentModificationException 異常,從而產生fail-fast機制。
fail-fast解決方案:(對於ArrayList)
方案一:在遍歷過程當中全部涉及到改變modCount值得地方所有加上synchronized或者直接使用Collections.synchronizedList,這樣就能夠解決。可是不推薦,由於增刪形成的同步鎖可能會阻塞遍歷操做。
方案二:使用CopyOnWriteArrayList來替換ArrayList。
CopyOnWriteArrayList全部可變操做(add、set 等等)都是經過對底層數組進行一次新的複製來實現的。
該類產生的開銷比較大,可是在兩種狀況下,它很是適合使用。1:在不能或不想進行同步遍歷,但又須要從併發線程中排除衝突時。2:當遍歷操做的數量大大超過可變操做的數量時。
CopyOnWriterArrayList根本就不會產生ConcurrentModificationException異常,也就是它使用迭代器徹底不會產生fail-fast機制。
public boolean add(E paramE) {
ReentrantLock localReentrantLock = this.lock;
localReentrantLock.lock();
try {
Object[] arrayOfObject1 = getArray();
int i = arrayOfObject1.length;
Object[] arrayOfObject2 = Arrays.copyOf(arrayOfObject1, i + 1);
arrayOfObject2[i] = paramE;
setArray(arrayOfObject2);
int j = 1;
return j;
} finally {
localReentrantLock.unlock();
}
}
final void setArray(Object[] paramArrayOfObject) {
this.array = paramArrayOfObject;
}
以上紅色的三句代碼使得CopyOnWriterArrayList不會拋ConcurrentModificationException異常。他們所展示的魅力就在於copy原來的array,再在copy數組上進行add操做,這樣作就徹底不會影響COWIterator中的array了。
任何對array在結構上有所改變的操做(add、remove、clear等),CopyOnWriterArrayList都會copy現有的數據,再在copy的數據上修改,這樣就不會影響COWIterator中的數據了,修改完成以後改變原有數據的引用便可。同時這樣形成的代價就是產生大量的對象,同時數組的copy也是至關有損耗的.