web-dev-qa-db-ja.com

コレクションに対するスレッドセーフな反復

_Collections.synchronizedXXX_(例:synchronizedSet())を使用すると、基になるコレクションの同期された「ビュー」が得られることは誰もが知っています。

ただし、これらのラッパー生成メソッドのドキュメントコレクションで明示的に同期する必要があることを示していますイテレーターを使用してコレクションを反復する場合。

この問題を解決するためにどのオプションを選択しますか?

私は次のアプローチしか見ることができません:

  1. ドキュメントに記載されているとおりに実行します。コレクションで同期します。
  2. iterator()を呼び出す前にコレクションのクローンを作成します
  3. イテレータがスレッドセーフであるコレクションを使用します(私はCopyOnWriteArrayList/Setしか認識していません)

そしておまけの質問として:同期ビューを使用する場合-foreach/Iterableの使用はスレッドセーフですか?

19
MRalwasser

あなたはすでにあなたのボーナスの質問に本当に答えました:いいえ、拡張されたforループを使用しますそうではありません安全です-それはイテレータを使用するからです。

どちらが最も適切なアプローチであるかについては、実際にはコンテキストによって異なります。

  • 書き込みは非常にまれですか?もしそうなら、CopyOnWriteArrayListが最も適切かもしれません。
  • コレクションは適度に小さく、反復は迅速ですか? (つまり、ループ内で多くの作業を行っていない場合)そうであれば、同期は問題ない可能性があります-特にこれがあまり頻繁に発生しない場合(つまり、コレクションの競合があまりない場合)。
  • 多くの作業を行っていて、同時に動作している他のスレッドをブロックしたくない場合は、コレクションのクローンを作成することで問題が解決する可能性があります。
26
Jon Skeet

アクセスモデルによって異なります。同時実行性が低く、書き込みが頻繁な場合は、1が最高のパフォーマンスを発揮します。同時実行性が高く、書き込みの頻度が低い場合は、3が最高のパフォーマンスを発揮します。オプション2は、ほとんどすべての場合にパフォーマンスが低下します。

foreachiterator()を呼び出すため、まったく同じことが当てはまります。

6
OrangeDog

Java 5.0で追加された、反復中の同時アクセスをサポートする新しいコレクションの1つを使用できます。別のアプローチは、スレッドセーフなtoArrayを使用してコピーを作成することです(コピー中)。

Collection<String> words = ...
// enhanced for loop over an array.
for(String Word: words.toArray(new String[0])) {

}
4
Peter Lawrey

Collections.synchronizedXXXを削除し、クライアントコードですべてのロックを均一に処理することをお勧めします。基本コレクションは、スレッドコードで役立つ種類の複合操作をサポートしていません。また、Java.util.concurrent.*を使用しても、コードはより困難です。できるだけ多くのコードをスレッドに依存しないようにすることをお勧めします。困難でエラーが発生しやすいスレッドセーフ(運が良ければ)コードを最小限に抑えます。

私はあなたの要件に完全に同意していないかもしれませんが、あなたがそれらに気付いていない場合は、「好意的な不変性」を念頭に置いて google-collections をチェックしてください。

1
vasquez

3つのオプションすべてが機能します。あなたの状況に適したものを選択することはあなたの状況が何であるかに依存します。

CopyOnWriteArrayListは、リストの実装が必要で、書き込むたびに基になるストレージがコピーされることを気にしない場合に機能します。非常に大きなコレクションがない限り、これはパフォーマンスにかなり適しています。

ConcurrentHashMapまたは "ConcurrentHashSet"(Collections.newSetFromMapを使用)は、MapまたはSetインターフェースが必要な場合に機能しますが、明らかに取得できません。この方法でランダムアクセス。素晴らしい!これら2つの点は、大きなデータセットでうまく機能することです。変更すると、基盤となるデータストレージのほんの一部をコピーするだけです。

1
Huw Roberts

この質問はかなり古いです(申し訳ありませんが、少し遅れています..)が、それでも回答を追加したいと思います。

私はあなたの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)もスレッドセーフについて心配する必要がないということです。

0
Thorben Kuck

それは、クローン作成/コピー/ toArray()を達成するために必要な結果に依存し、新しいArrayList(..)などはスナップショットを取得し、コレクションをロックしますnot。同期(コレクション)と反復を使用して、反復の終わりまでに確実にすることは変更されません。つまり、効果的にロックされます。

補足:(toArray()は通常、一時的なArrayListを内部的に作成する必要がある場合を除いて、推奨されます)。また、Collections.synchronizedXXXを使用して提供される場合、toArray()以外のものもsynchronized(collection)でラップする必要があることに注意してください。

0
bestsss