_Collections.synchronizedXXX
_(例:synchronizedSet()
)を使用すると、基になるコレクションの同期された「ビュー」が得られることは誰もが知っています。
ただし、これらのラッパー生成メソッドのドキュメントコレクションで明示的に同期する必要があることを示していますイテレーターを使用してコレクションを反復する場合。
この問題を解決するためにどのオプションを選択しますか?
私は次のアプローチしか見ることができません:
iterator()
を呼び出す前にコレクションのクローンを作成しますCopyOnWriteArrayList
/Setしか認識していません)そしておまけの質問として:同期ビューを使用する場合-foreach/Iterableの使用はスレッドセーフですか?
あなたはすでにあなたのボーナスの質問に本当に答えました:いいえ、拡張されたforループを使用しますそうではありません安全です-それはイテレータを使用するからです。
どちらが最も適切なアプローチであるかについては、実際にはコンテキストによって異なります。
CopyOnWriteArrayList
が最も適切かもしれません。アクセスモデルによって異なります。同時実行性が低く、書き込みが頻繁な場合は、1が最高のパフォーマンスを発揮します。同時実行性が高く、書き込みの頻度が低い場合は、3が最高のパフォーマンスを発揮します。オプション2は、ほとんどすべての場合にパフォーマンスが低下します。
foreach
はiterator()
を呼び出すため、まったく同じことが当てはまります。
Java 5.0で追加された、反復中の同時アクセスをサポートする新しいコレクションの1つを使用できます。別のアプローチは、スレッドセーフなtoArrayを使用してコピーを作成することです(コピー中)。
Collection<String> words = ...
// enhanced for loop over an array.
for(String Word: words.toArray(new String[0])) {
}
Collections.synchronizedXXX
を削除し、クライアントコードですべてのロックを均一に処理することをお勧めします。基本コレクションは、スレッドコードで役立つ種類の複合操作をサポートしていません。また、Java.util.concurrent.*
を使用しても、コードはより困難です。できるだけ多くのコードをスレッドに依存しないようにすることをお勧めします。困難でエラーが発生しやすいスレッドセーフ(運が良ければ)コードを最小限に抑えます。
私はあなたの要件に完全に同意していないかもしれませんが、あなたがそれらに気付いていない場合は、「好意的な不変性」を念頭に置いて google-collections をチェックしてください。
3つのオプションすべてが機能します。あなたの状況に適したものを選択することはあなたの状況が何であるかに依存します。
CopyOnWriteArrayList
は、リストの実装が必要で、書き込むたびに基になるストレージがコピーされることを気にしない場合に機能します。非常に大きなコレクションがない限り、これはパフォーマンスにかなり適しています。
ConcurrentHashMap
または "ConcurrentHashSet
"(Collections.newSetFromMap
を使用)は、Map
またはSet
インターフェースが必要な場合に機能しますが、明らかに取得できません。この方法でランダムアクセス。素晴らしい!これら2つの点は、大きなデータセットでうまく機能することです。変更すると、基盤となるデータストレージのほんの一部をコピーするだけです。
この質問はかなり古いです(申し訳ありませんが、少し遅れています..)が、それでも回答を追加したいと思います。
私はあなたの2番目の選択肢を選択します(つまり、iterator()を呼び出す前にコレクションのクローンを作成します)が、大きなひねりがあります。
イテレータを使用して反復する場合、.iterator()を呼び出す前にコレクションをコピーする必要はなく、イテレータパターンの概念を否定する(「否定」という用語を大まかに使用しています)が、次のように記述できます。 「ThreadSafeIterator」。
それは同じ前提で機能し、コレクションをコピーしますが、反復クラスに通知せずに、それを実行したことを通知します。このようなイテレータは次のようになります。
class ThreadSafeIterator<T> implements Iterator<T> {
private final Queue<T> clients;
private T currentElement;
private final Collection<T> source;
AsynchronousIterator(final Collection<T> collection) {
clients = new LinkedList<>(collection);
this.source = collection;
}
@Override
public boolean hasNext() {
return clients.peek() != null;
}
@Override
public T next() {
currentElement = clients.poll();
return currentElement;
}
@Override
public void remove() {
synchronized(source) {
source.remove(currentElement);
}
}
}
これをさらに一歩進めて、スレッドセーフなどを確保するためにSemaphore
クラスを使用することができます。しかし、一粒の塩で除去方法を取ります。
重要なのは、そのようなイテレータを使用することで、誰も、反復するクラスも反復するクラス(実際のWord)もスレッドセーフについて心配する必要がないということです。
それは、クローン作成/コピー/ toArray()を達成するために必要な結果に依存し、新しいArrayList(..)などはスナップショットを取得し、コレクションをロックしますnot。同期(コレクション)と反復を使用して、反復の終わりまでに確実にすることは変更されません。つまり、効果的にロックされます。
補足:(toArray()は通常、一時的なArrayListを内部的に作成する必要がある場合を除いて、推奨されます)。また、Collections.synchronizedXXXを使用して提供される場合、toArray()以外のものもsynchronized(collection)でラップする必要があることに注意してください。