【Java】CopyOnWriteArrayListとはなんぞや?

今回はCopyOnWriteArrayListの動作について確認してみたいと思います。

基本的にArrayListと同様に使えるのですが、addやremoveの内部処理の動作が異なります。

CopyOnWriteArrayListは配列のコピーを作成してadd操作などを実行(スレッドセーフで)することにより、イテレーション中であってもaddメソッド等の操作ができます。

通常のArrayListはイテレーション中に要素の追加をしようとしたりすると、ConcurrentModificationExceptionがスローされますが、CopyOnWriteArrayListでは問題なく要素を追加できます。

ただしイテレーション中に追加された要素に関しては、現状のイテレーションに影響を与えることはありません。

ではArrayListとCopyOnWriteArrayListでどのように動作が違うのか見ていきましょう。

まずはArrayListのサンプルコードを実行してみましょう。

import java.util.ArrayList;
import java.util.List;
 
public class Test {
	
	public static void main(String[] args) {
		List<Integer> arr = new ArrayList<Integer>();
		arr.add(1);
		arr.add(2);
		arr.add(3);
		
		//要素の中身を表示
		for ( int n : arr ) {
			
			//イテレーション中に要素の追加
			arr.add(n+10);
			
			//イテレーション対象の要素の値を表示
			System.out.println(n);
		}
		
		System.out.println("1ループ目終了");
		
		//要素の中身を再度表示
		for ( int n : arr ) {
			System.out.println(n);
		}
		
		System.out.println("2ループ目終了");
	}
}
実行結果
1
Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
	at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967)
	at Test.main(Test.java:13)

イテレーション中の操作は例外がスローされてしまいます。

ではCopyOnWriteArrayListのサンプルを実行してみましょう。

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
 
public class Test {
	
	public static void main(String[] args) {
		List<Integer> arr = new CopyOnWriteArrayList<Integer>();
		arr.add(1);
		arr.add(2);
		arr.add(3);
		
		//要素の中身を表示
		for ( int n : arr ) {
			
			//イテレーション中に要素の追加
			arr.add(n+10);
			
			//イテレーション対象の要素の値を表示
			System.out.println(n);
		}
		
		System.out.println("1ループ目終了");
		
		//要素の中身を再度表示
		for ( int n : arr ) {
			System.out.println(n);
		}
		
		System.out.println("2ループ目終了");
	}
}
実行結果
1
2
3
1ループ目終了
1
2
3
11
12
13
2ループ目終了

このようにイテレーション対象配列と元の配列が内部で別の参照となっているため、イテレーション中でも元の内容を操作できます。

マルチスレッドでアクセスする際は便利に使用できそうですね。

では元々ArrayListでイテレーション中に要素を削除することが可能だった、Iteratorからの操作はどうなるのでしょうか。

こちらもArrayListから試してみましょう。

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
 
public class Test {
	
	public static void main(String[] args) {
		List<Integer> arr = new ArrayList<Integer>();
		arr.add(1);
		arr.add(2);
		arr.add(3);
		
		//要素の中身を表示し、削除を行う
		Iterator<Integer> it = arr.iterator();
		while ( it.hasNext() ) {
			
			//要素取得
			int n = it.next();
			
			//3未満は削除
			if ( n < 3 ) {
				it.remove();
			}
			
			//イテレーション対象の要素の値を表示
			System.out.println(n);
		}
		
		System.out.println("1ループ目終了");
		
		//要素の中身を再度表示
		for ( int n : arr ) {
			System.out.println(n);
		}
		
		System.out.println("2ループ目終了");
	}
}
実行結果
1
2
3
1ループ目終了
3
2ループ目終了

うまいこと要素が削除できていますね。

続いてCopyOnWriteArrayList

import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
 
public class Test {
	
	public static void main(String[] args) {
		List<Integer> arr = new CopyOnWriteArrayList<Integer>();
		arr.add(1);
		arr.add(2);
		arr.add(3);
		
		//要素の中身を表示し、削除を行う
		Iterator<Integer> it = arr.iterator();
		while ( it.hasNext() ) {
			
			//イテレーション中に要素の追加
			int n = it.next();
			
			//3未満は削除
			if ( n < 3 ) {
				it.remove();
			}
			
			//イテレーション対象の要素の値を表示
			System.out.println(n);
		}
		
		System.out.println("1ループ目終了");
		
		//要素の中身を再度表示
		for ( int n : arr ) {
			System.out.println(n);
		}
		
		System.out.println("2ループ目終了");
	}
}
実行結果
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.concurrent.CopyOnWriteArrayList$COWIterator.remove(CopyOnWriteArrayList.java:1124)
	at Test.main(Test.java:22)

例外が発生してしまいました。

どうやらCopyOnWriteArrayListではIteratorからの操作はサポートされていないようですね。


やはり操作時にコピーを作るという点がある為、パフォーマンスが気になるところではありますし、積極的に利用するようなクラスではないかなぁ。

マルチスレッドでアクセスする際に便利ではあるので使いどころは見極めましょう。

Java

Posted by nompor