4

Javaコレクションのコンテンツをマルチスレッドで読みたいと思います。ここでは同じコンテキストで多くの質問がありましたが、特定の読み取りポイントについてはありません。

整数のコレクションがあります。複数のスレッドがそれを反復処理し、各スレッドが一度に1つの整数をプルするようにしたいだけです。すべてのコレクションが繰り返されていること、および2つの異なるスレッドによって整数が2回プルされていないことを確認したいと思います。

率直に言って、私は何がうまくいくのかわかりません。イテレータがスレッドセーフではないことは知っていますが、読み取り専用になるとわかりません。スレッド障害を取得するためにいくつかのテストを行いましたが、100%の確実性には達しませんでした。

int imax = 500;
Collection<Integer> li = new ArrayList<Integer>(imax);
for (int i = 0; i < imax; i++) {
    li.add(i);
}
final Iterator<Integer> it = li.iterator();

Thread[] threads = new Thread[20];
for (int i = 0; i < threads.length; i++) {
    threads[i] = new Thread("Thread " + i) {
        @Override
        public void run() {
            while(it.hasNext()) {
                System.out.println(it.next());
            }
        }
    };
}

for (int ithread = 0; ithread < threads.length; ++ithread) {
threads[ithread].setPriority(Thread.NORM_PRIORITY);
    threads[ithread].start();
}
try {
    for (int ithread = 0; ithread < threads.length; ++ithread)
    threads[ithread].join();
} catch (InterruptedException ie) {
    throw new RuntimeException(ie);
}

編集:実際のユースケースでは、この整数のそれぞれは、素数であるかどうかを見つけるなど、集中的な作業を開始するために使用されます。

上記の例では、重複やミスなしで整数のリストを取得していますが、それが偶然であるかどうかはわかりません。

ArrayListの代わりにHashSetを使用することも同様に機能しますが、これも偶然かもしれません。

一般的なコレクション(必ずしもリストである必要はありません)があり、そのコンテンツをマルチスレッド方式でプルする必要がある場合、実際にはどのように行いますか?

4

4 に答える 4

2

ユースケースでは、キューを使用することでメリットが得られます。たとえば、ArrayBlockingQueueなど、スレッドセーフな実装がいくつかあります。

Collection<Integer> li = new ArrayList<Integer>(imax);
final BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(li.size(), false, li);

Thread[] threads = new Thread[20];
for (int i = 0; i < threads.length; i++) {
    threads[i] = new Thread("Thread " + i) {
        @Override
        public void run() {
            Integer i;
            while ((i = queue.poll()) != null) {
                System.out.println(i);
            }
        }
    };
}

これはスレッドセーフであり、各スレッドは最初のコレクションの一部で他のスレッドから独立して動作できます。

于 2012-12-19T13:23:09.313 に答える
2

コレクションによって異なります。読み取り中に構造上の変化が発生していない場合は、同時に読み取ることができます。問題ありません。ほとんどのコレクションは、読み取りまたは反復のみの構造を変更しないため、問題ありませんが、使用するコレクションのドキュメントを必ず読んでから行ってください。

たとえば、HashSet javadocs

この実装は同期されていないことに注意してください。複数のスレッドが同時にハッシュセットにアクセスし、少なくとも1つのスレッドがセットを変更する場合は、外部で同期する必要があります。

これは、書き込みがない限り、2つのスレッドから同時に読み取ることが問題ないことを意味します。


これを行う1つの方法は、データを分割し、各スレッドにcollection.size()/ numberOfThreads要素を読み取らせることです。
スレッド#iはからcollection.size()/numThreads * iを読み取りますcollection.size()/numThreads * (i+1)

(最後の要素が失われないように特別な注意が必要です。これは、最後のスレッドのfrpmcollection.size()/numThreads * icollection.size()に設定することで実行できますが、最後のスレッドの作業が大幅に増え、苦労しているスレッドを待つことになります)。

別のオプションは、間隔のタスクキューを使用することです。各スレッドは、キューが空でないときに要素を読み取り、指定された間隔で要素を読み取ります。キューは複数のスレッドによって同時に変更されるため、同期する必要があります。

于 2012-12-19T12:46:08.533 に答える
2

一般に、反復によるコンテンツの収集は、マルチスレッドで実行するのに十分なコストはかかりません。これは、コンテンツを取得した後にリストを使用して行う操作です。だからあなたがすべきことはこれです:

  1. シングルスレッドを使用してコンテンツを取得し、ワークロードを分割します。
  2. いくつかのスレッド/ジョブを開始して処理を実行し、それらに(大きな)ワークロードを与えます。スレッドが元のリストを使用していないことを確認してください。
  3. 単一のスレッドを使用して結果を結合します。

コレクションを共有する必要がある場合は、スレッドセーフなコレクションを使用してください。これらは、Collections.synchronized ...関数を使用して作成できます。ただし、これはスレッドが互いに待機する必要があることを意味し、かなりの作業がない場合は、プログラムがシングルスレッドバージョンよりも遅くなることに注意してください。

スレッド間で共有するすべてのオブジェクトは、スレッドセーフである必要があることに注意してください(たとえば、すべてのアクセスを同期ブロックでラップすることによって)。それに関する最良の情報源は、実際の並行性です。

于 2012-12-19T12:47:52.570 に答える
1

から入手可能な同期バージョンを使用できますjava.util.Collectionsjava.util.concurrentまたは、 (eg )で特別なデータ構造を試すことができますConcurrentHashMap

私は自分で転がすよりもどちらかを好みます。

もう1つの考えは、コレクションアクセスだけでなく、必要に応じてメソッド全体を同期することです。

また、不変オブジェクトは常にスレッドセーフであることを忘れないでください。共有された可変状態を同期する必要があるだけです。

于 2012-12-19T12:45:48.820 に答える