Java Language
コンカレントコレクション
サーチ…
前書き
スレッドセーフなコレクション
デフォルトでは、さまざまなCollection型はスレッドセーフではありません。
しかし、コレクションをスレッドセーフにすることはかなり簡単です。
List<String> threadSafeList = Collections.synchronizedList(new ArrayList<String>());
Set<String> threadSafeSet = Collections.synchronizedSet(new HashSet<String>());
Map<String, String> threadSafeMap = Collections.synchronizedMap(new HashMap<String, String>());
スレッドセーフなコレクションを作成するときは、スレッドセーフなラッパーを介してのみ、元のコレクションからアクセスしないでください。
Java 5以降、 java.util.collections
は、さまざまなCollections.synchronized
メソッドが不要な新しいスレッドセーフなコレクションがいくつかあります。
List<String> threadSafeList = new CopyOnWriteArrayList<String>();
Set<String> threadSafeSet = new ConcurrentHashSet<String>();
Map<String, String> threadSafeMap = new ConcurrentHashMap<String, String>();
コンカレントコレクション
並行コレクションは、スレッドセーフなコレクションの一般化であり、並行環境での幅広い使用を可能にします。
スレッドセーフなコレクションは複数のスレッドから安全に要素を追加または削除できますが、必ずしも同じコンテキストで安全な反復処理を行う必要はありません(1つのスレッドでコレクションを安全に反復できない場合があります。要素を削除する)。
これは、同時収集が使用される場所です。
繰り返しはaddAll
、 removeAll
、またはコレクションのコピー(コンストラクタやその他の手段による)、並べ替えなど、コレクション内のいくつかのバルクメソッドの基本実装であることが多いため、並行コレクションのユースケースは実際かなり大きいです。
たとえば、Java SE 5 java.util.concurrent.CopyOnWriteArrayList
スレッドセーフと同時であるLis
トンの実装、そのJavadocは述べています:
スナップショットスタイルのイテレータメソッドは、イテレータが作成された時点で配列の状態への参照を使用します。この配列は、イテレータの存続期間中は変更されないため、干渉は不可能であり、イテレータはConcurrentModificationExceptionをスローしないことが保証されています。
したがって、次のコードは安全です。
public class ThreadSafeAndConcurrent {
public static final List<Integer> LIST = new CopyOnWriteArrayList<>();
public static void main(String[] args) throws InterruptedException {
Thread modifier = new Thread(new ModifierRunnable());
Thread iterator = new Thread(new IteratorRunnable());
modifier.start();
iterator.start();
modifier.join();
iterator.join();
}
public static final class ModifierRunnable implements Runnable {
@Override
public void run() {
try {
for (int i = 0; i < 50000; i++) {
LIST.add(i);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static final class IteratorRunnable implements Runnable {
@Override
public void run() {
try {
for (int i = 0; i < 10000; i++) {
long total = 0;
for(Integer inList : LIST) {
total += inList;
}
System.out.println(total);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
反復に関するもう1つの同時収集はConcurrentLinkedQueue
です。
イテレータは弱く一貫しており、イテレータの作成時または作成時のある時点でキューの状態を反映する要素を返します。彼らはjava.util.ConcurrentModificationExceptionを投げず、他の操作と並行して進めることができます。イテレータの作成以降にキューに含まれる要素は、一度だけ返されます。
コレクションが並行しているかどうかを確認するには、javadocをチェックする必要があります。 iterator()
メソッド( "fail fast"、 "weakly consistent"などiterator()
によって返されるイテレータの属性は、最も重要な属性です。
スレッドセーフであるが非並行の例
上記のコードでは、 LIST
宣言を
public static final List<Integer> LIST = Collections.synchronizedList(new ArrayList<>());
最新のマルチCPU /コアアーキテクチャでは統計的に例外が発生する可能性があります。
Collections
ユーティリティメソッドからの同期化されたCollections
は、要素の追加/削除ではスレッドセーフですが、繰り返しはできません(それに渡される基底のコレクションは既に存在している場合を除きます)。
ConcurrentHashMapへの挿入
public class InsertIntoConcurrentHashMap
{
public static void main(String[] args)
{
ConcurrentHashMap<Integer, SomeObject> concurrentHashMap = new ConcurrentHashMap<>();
SomeObject value = new SomeObject();
Integer key = 1;
SomeObject previousValue = concurrentHashMap.putIfAbsent(1, value);
if (previousValue != null)
{
//Then some other value was mapped to key = 1. 'value' that was passed to
//putIfAbsent method is NOT inserted, hence, any other thread which calls
//concurrentHashMap.get(1) would NOT receive a reference to the 'value'
//that your thread attempted to insert. Decide how you wish to handle
//this situation.
}
else
{
//'value' reference is mapped to key = 1.
}
}
}