#navi_header|Java| Set操作、特に変更操作と拡張for構文を使ったiterationを別スレッドで処理する時のメモ。 * java.util.ConcurrentModificationException 例外が発生するNG例(SetTest1.java) int値フィールド一つだけの簡単なSomeObjectをSetにaddするWriteThreadと、Setから拡張for構文でiterationし、int値の合計を表示するEnumThreadの二つを起動する。 SetTest1.java: #code|java|> import java.util.*; import java.util.concurrent.*; public class SetTest1 { final Set objects = Collections.synchronizedSet(new HashSet()); Random rnd = new Random(); public static void main(String args[]) { new SetTest1().test(); } public void test() { new EnumThread().start(); new WriteThread().start(); } class SomeObject { int val; public SomeObject(int val_) { val = val_; } public int getVal() { return val; } } class EnumThread extends Thread { public void run() { while (true) { long sum = 0; for (SomeObject o : objects) { sum += o.getVal(); } System.out.println("sum = " + sum); try { sleep(100); } catch (InterruptedException e) { } } } } class WriteThread extends Thread { public void run() { while (true) { objects.add(new SomeObject(rnd.nextInt(10))); try { sleep(10); } catch (InterruptedException e) { } } } } } ||< Collections.synchronizedSet()はSetに対する操作を同期化してくれるが、iteration経由での操作については対象外。 実行してみると、かなり早いタイミングで java.util.ConcurrentModificationException が発生してしまう。 #pre||> > javac SetTest1.java > java SetTest1 sum = 0 sum = 48 sum = 78 sum = 128 sum = 175 sum = 217 Exception in thread "Thread-0" java.util.ConcurrentModificationException at java.util.HashMap$HashIterator.nextEntry(HashMap.java:793) at java.util.HashMap$KeyIterator.next(HashMap.java:828) at SetTest1$EnumThread.run(SetTest1.java:35) ||< * iterationについて手動でsynchronized()してみる(SetTest2.java) java.util.Collections.synchronizedSet()のJavaDocにも記載されているが、iteratorを使う時は手動でコレクションに対するsynchronized()で囲ってみる。 SetTest2.java: #code|java|> // ... class EnumThread extends Thread { public void run() { while (true) { long sum = 0; synchronized(objects) { for (SomeObject o : objects) { sum += o.getVal(); } } System.out.println("sum = " + sum); try { sleep(100); } catch (InterruptedException e) { } } } } // ... ||< これであれば、ConcurrentModificationException 例外は発生しない。 * 明らかに変更操作よりもiterationの方が多い場合のCopyOnWriteArraySet使用例(SetTest3.java) 複数スレッドからの操作で、全体として変更操作よりもiteration操作の方が明らかに多い場合は java.util.concurrent.CopyOnWriteArraySet を使ってみる。iteration中の別スレッドからの変更(追加/削除)は、配列をコピーして行われる。変更操作はiteratorに反映されないため、iteratorは"スナップショット"として使うことになる。iteratorに対する要素変更はサポートされず、実行するとUnsupportedOperationExceptionがthrowされる。 #code|java|> //... (SetTest1.javaと同じ) public class SetTest3 { final Set objects = new CopyOnWriteArraySet(); Random rnd = new Random(); public static void main(String args[]) { new SetTest3().test(); } //... (SetTest1.javaと同じ) ||< 手動でsynchronized()で囲うよりは、CopyOnWriteの方が個人的には好み。 #navi_footer|Java|