web-dev-qa-db-ja.com

Javaコレクションへの同時アクセスを制御するための最良の方法

古い同期されたベクターコレクション、同期されたアクセスを持つArrayList、Collections.synchronizedList、または同時アクセスのための他のソリューションを使用する必要がありますか?

関連する質問にも検索にも私の質問が表示されません( コレクションをスレッドセーフにしますか? 同じではありません)。

最近、アプリケーションのGUI部分で一種の単体テストを行う必要がありました(基本的にAPIを使用してフレームを作成したり、オブジェクトを追加したりします)。これらの操作はユーザーよりもはるかに高速に呼び出されるため、まだ作成されていない、またはすでに削除されているリソースにアクセスしようとするメソッドに多くの問題があります。

EDTで発生する特定の問題は、別のスレッドでビューのリンクリストを変更しながらウォークすることから発生しました(他の問題の中でもConcurrentModificationExceptionが発生します)。単純な配列リストではなくリンクリストである理由を聞かないでください(一般に0または1のビューが含まれているため、さらに少なくなります...)。そのため、質問ではより一般的なArrayListを使用しました(古いいとこ)。

とにかく、並行性の問題にあまり精通していないので、私は少し情報を調べて、古い(そしておそらく時代遅れの)Vector(設計により同期された操作を持っている)、synchronized (myList) { }を持つArrayListから何を選ぶべきか疑問に思いましたクリティカルセクションの周囲(追加/削除/ウォーク操作)またはCollections.synchronizedListによって返されるリストの使用(後者の使用方法さえわからない)。

もう1つの設計上の誤りは、オブジェクトを使用するメカニズムを提供する代わりに、オブジェクトを公開すること(getViewList()メソッド...)であったため、最終的に2番目のオプションを選択しました。

しかし、他のアプローチの長所と短所は何ですか?


[編集]ここにはたくさんの良いアドバイスがありますが、どれかを選ぶのは難しいです。私はより詳細で考えのためのリンク/食べ物を提供することを選択します... :-)私もダロンのものが好きです。

要約する:

  • 私が推測したように、Vector(およびその邪悪な双子であるHashtableもおそらく)はほとんど時代遅れであり、シングルスレッド環境でも強制される同期の遅さを超えて、古いデザインは新しいコレクションほど良くないと人々が言っ​​ているのを見てきました。それを維持する場合、それは主に古いライブラリ(およびJava API)の一部)がまだそれを使用しているためです。
  • 私が思っていたのとは異なり、Collections.synchronizedXxxxはVectorよりも現代的ではなく(Collectionsと現代的であるように見えます。つまり、Java 1.2!)、実際には良くありません。知っておくと便利です。要するに、私もそれらを避けるべきです。
  • 結局のところ、手動​​同期は良い解決策のようです。パフォーマンスの問題があるかもしれませんが、私の場合は重要ではありません。ユーザーアクションで実行される操作、小さなコレクション、頻繁な使用はありません。
  • Java.util.concurrentパッケージ、特にCopyOnWriteメソッドは覚えておく価値があります。

私はそれを正しく理解したと思います... :-)

25
PhiLho

Collections.synchronizedList()によって返されるVectorとListは、道徳的に同じものです。 Vectorは事実上(実際にはそうではありませんが)非推奨であり、代わりに常に同期リストを好むと思います。唯一の例外は、Vectorを必要とする古いAPI(特にJDKのAPI)です。

ネイキッドArrayListを使用し、個別に同期することで、同期をより正確に調整する機会が得られます(相互に排他的なブロックに追加のアクションを含めるか、リストへの複数の呼び出しを1つのアトミックアクションにまとめます)。欠点は、同期の外部でネイキッドArrayListにアクセスするコードを記述できることです。これは壊れています。

検討したいもう1つのオプションは、CopyOnWriteArrayListです。これにより、Vectorや同期されたArrayListのようにスレッドセーフが得られますが、データの非ライブスナップショットで動作しているため、ConcurrentModificationExceptionをスローしないイテレーターも提供されます。

これらのトピックに関するこれらの最近のブログのいくつかは興味深いと思うかもしれません:

23
Alex Miller

Java Concurrency inPractice "」という本を強くお勧めします。

それぞれの選択肢には長所/短所があります。

  1. ベクトル-「廃止」と見なされます。それは、より多くの主流のコレクションよりも注意とバグ修正が少ないかもしれません。
  2. 独自の同期ブロック-間違いを犯しやすい。多くの場合、以下の選択肢よりもパフォーマンスが低下します。
  3. Collections.synchronizedList()-専門家によって行われた選択肢2。アトミックである必要があるマルチステップ操作(取得/変更/設定または反復)のため、これはまだ完了していません。
  4. Java.util.concurrentの新しいクラス-多くの場合、選択肢3よりも効率的なアルゴリズムがあります。マルチステップ操作に関する同様の警告が適用されますが、役立つツールが提供されることがよくあります。
13
Darron

VectorよりもArrayListを好む正当な理由は考えられません。 Vectorのリスト操作は同期されます。つまり、複数のスレッドが安全に変更できます。そして、あなたが言うように、ArrayListの操作はCollections.synchronizedListを使用して同期することができます。

同期リストを使用している場合でも、別のスレッドによって変更されているときにコレクションを反復処理すると、ConcurrentModificationExceptionsが発生することに注意してください。したがって、そのアクセスを外部で調整することが重要です(2番目のオプション)。

反復の問題を回避するために使用される一般的な手法の1つは、コレクションの不変のコピーを反復することです。 Collections.unmodifiableListを参照してください

10
jcrossley3

私は常にJava.util.concurrent( http://Java.Sun.com/javase/6/docs/api/Java/util/concurrent/package-summary.html )パッケージにアクセスしますまず、適切なコレクションがあるかどうかを確認します。 Java.util.concurrent.CopyOnWriteArrayListクラスは、リストにほとんど変更を加えず、反復回数を増やす場合に適しています。

また、VectorとCollections.synchronizedListがConcurrentModificationExceptionを防ぐとは思わない。

適切なコレクションが見つからない場合は、独自の同期を行う必要があります。反復するときにロックを保持したくない場合は、コピーを作成してコピーを反復することを検討してください。

7
richs

Vectorによって返されるIteratorが同期されているとは思いません。つまり、Vectorは、1つのスレッドが基になるコレクションを変更していないことを(それ自体で)保証できません。別のスレッドがそれを繰り返している間。

反復がスレッドセーフであることを保証するには、同期を自分で処理する必要があると思います。同じVector/List/objectが複数のスレッドで共有されている(したがって問題が発生している)と仮定すると、そのオブジェクト自体で同期することはできませんか?

3
matt b

最も安全な解決策は、共有データへの同時アクセスを完全に回避することです。非EDTスレッドが同じデータを操作する代わりに、変更を実行するRunnableを使用してSwingUtilities.invokeLater()を呼び出すようにします。

真剣に、共有データの同時実行性は、どこかに隠れている別の競合状態やデッドロックがないかどうかわからない毒蛇の巣であり、最悪の場合にあなたを尻に噛む時間を競います。

2

CopyOnWriteArrayListは一見の価値があります。これは、通常から読み取られるリスト用に設計されています。書き込むたびに、裏で新しい配列が作成されるため、配列全体で反復するものはConcurrentModificationExceptionを取得しません。

2
Javamann