1. 概要

このクイック記事では、 java.util.concurrentパッケージのCopyOnWriteArrayListについて説明します。

これは、マルチスレッドプログラムで非常に便利な構成です。明示的な同期を行わずに、スレッドセーフな方法でリストを反復処理する場合です。

2. CopyOnWriteArrayList API

CopyOnWriteArrayList の設計では、興味深い手法を使用して、同期を必要とせずにスレッドセーフにします。 add() remove()などの変更メソッドのいずれかを使用している場合、 CopyOnWriteArrayListのコンテンツ全体が新しい内部にコピーされますコピー。

この単純な事実により、同時変更が発生している場合でも、安全な方法でリストを反復処理できます

CopyOnWriteArrayListでiterator()メソッドを呼び出すと、は、のコンテンツの不変のスナップショットによってバックアップされたIteratorを取得します。 ]CopyOnWriteArrayList

その内容は、Iteratorが作成された時点からのArrayList内にあるデータの正確なコピーです。 その間に他のスレッドがリストから要素を追加または削除した場合でも、その変更により、そのリストからの以降のデータ検索で使用されるデータの新しいコピーが作成されます。

このデータ構造の特性により、変更するよりも頻繁にデータ構造を反復処理する場合に特に役立ちます。 このシナリオで要素の追加が一般的な操作である場合、 CopyOnWriteArrayList は適切な選択ではありません。これは、追加のコピーが確実に標準以下のパフォーマンスにつながるためです。

3. 挿入中にCopyOnWriteArrayListを反復処理

整数を格納するCopyOnWriteArrayListのインスタンスを作成しているとしましょう。

CopyOnWriteArrayList<Integer> numbers 
  = new CopyOnWriteArrayList<>(new Integer[]{1, 3, 5, 8});

次に、その配列を反復処理するため、イテレータインスタンスを作成します。

Iterator<Integer> iterator = numbers.iterator();

Iterator が作成された後、番号リストに新しい要素を追加します。

numbers.add(10);

CopyOnWriteArrayListのイテレーターを作成すると、 iterator()が呼び出されたときにリスト内のデータの不変のスナップショットが取得されることに注意してください。

そのため、繰り返し処理している間、繰り返しに10という数字は表示されません。

List<Integer> result = new LinkedList<>();
iterator.forEachRemaining(result::add);
 
assertThat(result).containsOnly(1, 3, 5, 8);

新しく作成されたIteratorを使用した後続の反復でも、追加された数値10が返されます。

Iterator<Integer> iterator2 = numbers.iterator();
List<Integer> result2 = new LinkedList<>();
iterator2.forEachRemaining(result2::add);

assertThat(result2).containsOnly(1, 3, 5, 8, 10);

4. 反復中の削除は許可されていません

CopyOnWriteArrayList は、基になるリストが変更された場合でも要素を安全に反復できるようにするために作成されました。

コピーメカニズムのため、返されたイテレータに対する remove()操作は許可されていません。その結果、 UnsupportedOperationException:が発生します。

@Test(expected = UnsupportedOperationException.class)
public void whenIterateOverItAndTryToRemoveElement_thenShouldThrowException() {
    
    CopyOnWriteArrayList<Integer> numbers
      = new CopyOnWriteArrayList<>(new Integer[]{1, 3, 5, 8});

    Iterator<Integer> iterator = numbers.iterator();
    while (iterator.hasNext()) {
        iterator.remove();
    }
}

5. 結論

このクイックチュートリアルでは、java.util.concurrentパッケージからのCopyOnWriteArrayListの実装を確認しました。

このリストの興味深いセマンティクスと、他のスレッドがリストから要素を挿入または削除し続けることができる間、スレッドセーフな方法でそれを繰り返す方法を見ました。

これらすべての例とコードスニペットの実装は、 GitHubプロジェクトにあります。これはMavenプロジェクトであるため、そのままインポートして実行するのは簡単です。