JAVA

MapをIteratorで回すとConcurrentModificationExceptionが発生する

2014年5月22日

JAVAにてMapをIteratorで回していたのですが、デバッグでIteratorの中を確認しているとConcurrentModificationExceptionが発生しました。

ConcurrentModificationExceptionはこちらで確認してみるとオブジェクトの並列変更を行うとエラーが発生するそうです。

デバック中Eclipseの式でデータ中身を表示した瞬間に例外が投げられるのはそのせいかな……
Iteratorはfail fastのため処理中に別スレッドでCollectionを変更したらダメだそうです。

対策としては同期化(スレッドセーフ)を行えばよいと思います。

同期化方法としてCollections.synchronizedMapを使うだと思います。

ただ、Iteratorを使う場合、要素数が変動されるとConcurrentModificationExceptionが発生してしまう可能性があります。

Map<String, String> test = new HashMap<String, String>();
Iterator<Map.Entry<String, String>> testIterator = Collections.synchronizedMap(test).entrySet().iterator();
while(testIterator.hasNext()){
	Map.Entry<String, String> testEntry = (Map.Entry<String, String>) testIterator.next();
	String testId = (String)testEntry.getKey();
	String testValue = (String)testEntry.getValue();
}

↑だとダメらしい(Oracle Collectionsより)

Map<String, String> test = Collections.synchronizedMap(new HashMap<String, String>());
Set<Entry<String, String>> testSet = test.entrySet();
synchronized(test) {
	Iterator<Entry<String, String>> testIterator = testSet.iterator();
	while(testIterator.hasNext()){
		Map.Entry<String, String> testEntry = (Map.Entry<String, String>) testIterator.next();
		String testId = (String)testEntry.getKey();
		String testValue = (String)testEntry.getValue();
	}
}

のほうがいいかも。

keySetはsynchronizedより前に書くみたいなのでentrySetも同様と思います。

synchronizedMapを行う場合、Setはsynchronizedより

今回はCollections.synchronizedMapを例にしましたが他にも
Collections.synchronizedList
Collections.synchronizedSet
もあるそうです。(Oracle Collectionsより)

ConcurrentHashMapを使えば同期化処理が高速になるそうですが、通常のMapとは少し異なるのでえいやぁ!で一括置換するには危険だと思いますし、万能というわけでもないようです。(Java の ConcurrentHashMap における同期化より)

同期化処理難しいですね。

-JAVA
-, , , ,