android スレッドセーフはじめの一歩(4)

ArrayListはスレッドセーフではないんですね。次の2つの問題があります。

(1)add()やremove()がスレッドセーフではない。
複数スレッドから同時に、add()やremove()をするようなプログラムを試したところ、size()と内容が合わなくなることがありました。

(2)イテレーション操作の途中で、add()やremove()すると、ConcurrentModificationExceptionが発生します。なお、同じスレッドでも発生します。同じスレッドなら、すぐに気づくので、イテレーション途中のadd()やremove()は書くことはないんですね。

Collections.synchronizedList

(1)については、synchronizedブロックで、add()やremove()を囲む方法があります。しかし、全ての箇所のadd()やremove()をsynchronizedブロックで囲む必要があります。

そこで、synchronizedListを次のように使います。

List<String> list = new ArrayList<>();
↓
List<String> list = Collections.synchronizedList(new ArrayList<>());

listは、add()やremove()など、要素数を変更する操作は、スレッドセーフになりました。

ただし、synchronizedListでも、(2)のConcurrentModificationExceptionは発生します。

この問題には、synchronizedブロックで囲むか、CopyOnWriteArrayListを使います。

synchronized (list) {
    for (String item : list) {
        System.out.println(item);
    }
}

synchronized (list) {
    Iterator<String> ite = list.iterator();
    while (ite.hasNext()) {
        System.out.println(ite.next());
    }
}

CopyOnWriteArrayList

次のように使います。

List<String> list = new CopyOnWriteArrayList<>();

add()やremove()はスレッドセーフですし、イテレーション途中のadd()やremove()をしても、ConcurrentModificationExceptionは発生しません。

ただし、イテレータを取得後、Listに変更を加えても、イテレータに反映されません。

List<String> list = new CopyOnWriteArrayList<>();
list.add("A");

Iterator<String> ite = list.iterator();
list.add(0, "B");
list.add("X");

// "B"ではなく、"A"が表示される。
// "X"は表示されない。
while (ite.hasNext()) {
    System.out.println(ite.next());  
}

タイトルとURLをコピーしました