java.util.ConcurrentModificationException異常
1、
今天在寫一個帶緩衝功能的訪問代理程式時出現了java.util.ConcurrentModificationException異常,因為該異常是非捕獲型異常而且很少見,所以費了些時間才找到問題所在,原來在通過Iterator進行遍曆的時候,如果直接對HashMap進行操作後,再繼續用之前的Iterator進行遍曆就會出現這個異常,表示其HashMap已經被修改。
來源程式程式碼片段如下:caches為一個HashMap對象
String sameKeyPart = accesserClassName + "@" + methodName + "@" +parameterKeyString + "@"; Iterator keys = caches.keySet().iterator(); String key = null; while (keys.hasNext()) ...{ key = (String) keys.next(); if (key.startsWith(sameKeyPart)) ...{ caches.remove(key); } }
解決辦法為通過其相應的Iterator進行刪除就可以了,修改後程式碼片段如下:
String sameKeyPart = accesserClassName + "@" + methodName + "@" +parameterKeyString + "@"; Iterator keys = caches.keySet().iterator(); String key = null; while (keys.hasNext()) ...{ key = (String) keys.next(); if (key.startsWith(sameKeyPart)) ...{ keys.remove(); } }感覺這片文章說的很具體:
http://blog.csdn.net/myyate/article/details/1943040
Set<Object> bb = new HashSet<Object>(); bb.add("23242"); bb.add(Integer.valueOf(45)); Iterator it = bb.iterator(); while(it.hasNext()) { Object ele = it.next(); bb.remove(ele); //wrong } Set<Object> bb = new HashSet<Object>(); bb.add("23242"); bb.add(Integer.valueOf(45)); Iterator it = bb.iterator(); while(it.hasNext()) { it.next(); it.remove(); }
2、
撰寫多線程代碼時,你遇到過多少次下面的提示:
Exception in thread "main"java.util.ConcurrentModificationException
這個異常產生的原因有幾個。一是直接對集合調用刪除操作而不是在列舉程式上。二是不同的線程試圖對集合進行增刪操作的時候。
這個解決辦法的第一步就是同步代碼,使得你在枚舉的時候其它的線程不能增刪記錄。但是如果每個枚舉過程要進行複雜的計算或者是資料庫訪問的一部分的話,這個同步就會導致可怕的後果。為了減少負面影響,可以拷貝一個唯讀列舉程式,去掉同步,然後採用下列代碼所示的方法:
private List list; public void add(Object obj) { synchronized(list) { list.add(obj); } } public void perform( ) { Iterator iterator = null; synchronized(list) { iterator = new CopiedIterator(list.iterator()); } while(iterator.hasNext( )) { // perform resource or cpu hungry work } }
重要的是記住,CopiedIterator不是一個複製,只是一個唯讀拷貝,所以它並沒有保持原有的全部功能。最重要的是,不能再調用CopiedIterator.remove方法了。CopiedIterator.remove的實現如下:
public class CopiedIteratorimplements Iterator { private Iterator iterator = null; public CopiedIterator(Iterator itr) { LinkedList list = new LinkedList( ); while(itr.hasNext( )) { list.add(itr.next( )); } this.iterator = list.iterator( ); } public boolean hasNext( ) { return this.iterator.hasNext( ); } public void remove( ) { throw new UnsupportedOperationException("This isa read-only iterator. "); } public Object next( ) { return this.iterator.next( ); } }
列舉程式的唯讀拷貝將用在同步狀態上的時間減少到最小,因此可以增強全域的效率。
3、
當使用 fail-fast iterator 對 Collection 或 Map進行迭代操作過程中嘗試直接修改 Collection / Map的內容時,即使是在單線程下運行, java.util.ConcurrentModificationException 異常也將被拋出。
Iterator 是工作在一個獨立的線程中,並且擁有一個 mutex 鎖。Iterator被建立之後會建立一個指向原來對象的單鏈索引表,當原來的對象數量發生變化時,這個索引表的內容不會同步改變,所以當索引指標往後移動的時候就找不到要迭代的對象,所以按照fail-fast 原則 Iterator 會馬上拋出java.util.ConcurrentModificationException 異常。
所以 Iterator在工作的時候是不允許被迭代的對象被改變的。但你可以使用 Iterator本身的方法 remove() 來刪除對象, Iterator.remove()方法會在刪除當前迭代對象的同時維護索引的一致性。
有意思的是如果你的 Collection / Map對象實際只有一個元素的時候, ConcurrentModificationException異常並不會被拋出。這也就是為什麼在 javadoc 裡面指出: it would bewrong to write a program that depended on this exception for itscorrectness: ConcurrentModificationException should be used only todetect bugs.
importjava.util.*; public final classMyTest { private static HashMap p_mapList = newHashMap(2); privateMyTest(){} public static voidinit(){ // If only there are more than one element inMap, // the ConcurrentModificationException will notbe //thrown. p_mapList.put(new String("hello"),newString("world")); p_mapList.put(new String("goto"),newString("hell")); } public static void clear() throwsException{ Iterator pTmpKeys =null; LongpTmpKeyLong; pTmpKeys =p_mapList.keySet().iterator(); String pCurKey =null; String pCurObj =null; while(pTmpKeys.hasNext()){ pCurKey = (String)pTmpKeys.next(); pCurObj = (String)p_mapList.get(pCurKey); p_mapList.put(pCurKey,null); // You can not remove element in Map objectdirectly. //p_mapList.remove(pCurKey); // But you can remove current element by iteratoritself. pTmpKeys.remove(); System.out.println(pCurKey + "removed."); } System.out.println(p_mapList.size()+ " entries left afteriterator."); pTmpKeys =null; } public static void main(String[]args) throwsException{ MyTest.init(); MyTest.clear(); } }