home ホーム search 検索 -  login ログイン  | reload edit datainfo version cmd icon diff delete  | help ヘルプ

Java/Collections/Setの同期化メモ (v1)

Java/Collections/Setの同期化メモ (v1)

Java / Collections / Setの同期化メモ (v1)
id: 802 所有者: msakamoto-sf    作成日: 2010-10-05 15:09:49
カテゴリ: Java 

Set操作、特に変更操作と拡張for構文を使ったiterationを別スレッドで処理する時のメモ。

java.util.ConcurrentModificationException 例外が発生するNG例(SetTest1.java)

int値フィールド一つだけの簡単なSomeObjectをSetにaddするWriteThreadと、Setから拡張for構文でiterationし、int値の合計を表示するEnumThreadの二つを起動する。

SetTest1.java:

import java.util.*;
import java.util.concurrent.*;
 
public class SetTest1 {
    final Set<SomeObject> objects = 
        Collections.synchronizedSet(new HashSet<SomeObject>());
 
    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 が発生してしまう。

> 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:

// ...
    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<E> を使ってみる。iteration中の別スレッドからの変更(追加/削除)は、配列をコピーして行われる。変更操作はiteratorに反映されないため、iteratorは"スナップショット"として使うことになる。iteratorに対する要素変更はサポートされず、実行するとUnsupportedOperationExceptionがthrowされる。

//... (SetTest1.javaと同じ)
public class SetTest3 {
    final Set<SomeObject> objects = 
        new CopyOnWriteArraySet<SomeObject>();
 
    Random rnd = new Random();
 
    public static void main(String args[])
    {
        new SetTest3().test();
    }
//... (SetTest1.javaと同じ)

手動でsynchronized()で囲うよりは、CopyOnWriteの方が個人的には好み。



プレーンテキスト形式でダウンロード
現在のバージョン : 1
更新者: msakamoto-sf
更新日: 2010-10-05 15:27:17
md5:b679c1c7b8eaf499722dddee052997c7
sha1:b62d000871de94d2aeb45c56c1febc975592e0c1
コメント
コメントを投稿するにはログインして下さい。