コレクション クラスを設計するとき、ロックをプライベートに実装してスレッド セーフにしない理由はありますか? それとも、その責任はコレクションの消費者に任せるべきですか?
17 に答える
スレッドセーフにするためにロックをプライベートに実装しない理由はありますか?
場合によります。複数のスレッドからアクセスされるコレクション クラスを作成することが目標ですか? その場合は、スレッドセーフにします。そうでない場合は、時間を無駄にしないでください。この種のことは、人々が「時期尚早の最適化」について話すときに言及するものです
あなたが抱えている問題を解決してください。未来が見えないので、何年か先に起こるかもしれないと思っている未来の問題を解決しようとしないでください。
注: それでも、コレクションにロックを追加する必要があったとしてもそれほど難しくないように、保守可能な方法でコードを作成する必要があります。私のポイントは、「必要のない、使用しない機能を実装しない」ということです
Java の場合は、速度を上げるために非同期のままにしておく必要があります。コレクションのコンシューマーは、必要に応じて同期ラッパーでラップできます。
スレッド セーフ コレクションは欺くことができます。Jared Par は、スレッド セーフ コレクションに関するいくつかの興味深い記事を投稿しました。
問題は、スレッド セーフ コレクションにいくつかのレベルがあることです。ほとんどの人がスレッド セーフ コレクションと言うとき、その本当の意味は「複数のスレッドから変更およびアクセスされた場合に破損しないコレクション」であることがわかります。</p>
...
しかし、データ スレッド セーフ リストの作成が非常に簡単であるなら、Microsoft がこれらの標準コレクションをフレームワークに追加しないのはなぜでしょうか?
回答: ThreadSafeList は事実上使用できないクラスです。これは、設計が悪いコードにつながるためです。
この設計の欠陥は、リストが一般的にどのように使用されているかを調べるまで明らかになりません。たとえば、リストに最初の要素があればそれを取得しようとする次のコードを見てください。
static int GetFirstOrDefault(ThreadSafeList<int> list) {
if (list.Count > 0) {
return list[0];
}
return 0; }
このコードは、典型的な競合状態です。リストに > 要素が 1 つしかない場合を考えてみましょう。別のスレッドが if ステートメントと return ステートメントの間でその要素を削除すると、return ステートメントはリスト内の無効なインデックスにアクセスしようとしているため、例外がスローされます。ThreadSafeList はデータ スレッド セーフですが、ある呼び出しの戻り値が同じオブジェクトへの次の呼び出しで有効であることを保証するものは何もありません。
http://blogs.msdn.com/b/jaredpar/archive/2009/02/11/why-are-thread-safe-collections-so-hard.aspx
http://blogs.msdn.com/b/jaredpar/archive/2009/02/16/a-more-usable-thread-safe-collection.aspx
コレクション クラスは、できるだけ高速である必要があります。したがって、ロックを外したままにします。
呼び出し元のコードは、コレクション クラスが認識していないロックが最適な場所を認識します。最悪の場合、アプリは追加のロックを追加する必要があります。つまり、2 つのロックが発生し、パフォーマンス ヒットが 2 倍になります。
スレッドセーフにしないことをドキュメントで明確にして、それを省略してください。または、アプリケーションでスレッドセーフが必要な場合は、スレッドセーフにして、ドキュメントにその旨を記載してください。唯一のルールは、それを文書化することです。それ以外は、あなたのためにクラスを作成し、他の人がそれを使用したい場合は、使用できます。
コレクション クラスを探していて、スレッド セーフ機能が必要であり、あなたのクラスにそれらがない場合は、すぐに次の製品にスキップして、それらが提供するものを確認します。あなたのコレクションはもう私の注意を引くことはありません。
冒頭の「If」に注意してください。それを望む顧客もいれば、望まない顧客もいれば、気にしない顧客もいます。消費者向けのツール キットを作成する場合は、両方の種類を提供してみませんか? そうすれば、どちらを使用するかを選択できますが、スレッドセーフが必要な場合でも注意が必要であり、自分で記述する必要はありません。
個人的には消費者に任せます。コレクションクラスをよりジェネリックにします。
コレクションをスレッドセーフにすることが、Java の Vector および Hashtable クラスを殺した原因です。クライアントにとって、クラスがアクセスされるたびに同期ヒットを取得するよりも、以前に提案されたようにスレッドセーフなラッパーでラップするか、メソッドのサブセットでデータ アクセスを同期する方がはるかに簡単です。Vector や Hashtable を使用する人はほとんどいません。使用した場合、それらの代替 (ArrayList および HashMap) は非常に高速であるため、笑われます。私 (C++ のバックグラウンドを持っている) は "Vector" 名 (STL) を好むので、これは残念ですが、ArrayList はそのままです。
基本的に、クラスの 2 つのメソッドである lock() と unlock() にロックを実装して、コレクションをスレッド セーフとして設計します。必要な場所に呼び出しますが、空のままにします。次に、lock() および unlock() メソッドを実装するコレクションをサブクラス化します。1つの価格で2つのクラス。
スレッドセーフにしない主な理由はパフォーマンスです。スレッド セーフ コードは、非セーフ コードより数百倍も遅くなる可能性があるため、クライアントがその機能を必要としない場合、それはかなりの無駄です。
クラスをスレッドセーフにしようとする場合は、一般的な使用シナリオを決定する必要があることに注意してください。
たとえば、コレクションの場合、すべてのプロパティとメソッドを個別にスレッドセーフにするだけでは、消費者にとって十分ではない可能性があります。それを読んだ後にカウントが変わりました。
コレクションをスレッドセーフにしない本当の理由は、シングルスレッドのパフォーマンスを向上させるためです。例: Vector 上の ArrayList。スレッド セーフを呼び出し元に委ねると、ロックを回避することで、非同期のユース ケースを最適化できます。
コレクションをスレッドセーフにする本当の理由は、マルチスレッドのパフォーマンスを改善するためです。例: HashMap 上の ConcurrentHashMap。CHM はマルチスレッドの問題を内部化するため、ロックをストライピングして、外部同期よりも効果的に同時アクセスを増やすことができます。
これが良いスタートです。
しかし、コレクションの優れた機能の1つである列挙が失われていることに気付くでしょう。列挙子をスレッドセーフすることはできません。コレクション自体へのインスタンスロックを保持する独自の列挙子を実装しない限り、実際には実行可能ではありません。これにより、大きなボトルネックと潜在的なデッドロックが発生する可能性があります。
これにより、触れた要素が他の誰にも使用されていないことがわかっている場合でも、複数のスレッドからコレクションに同時にアクセスすることができなくなります。
例としては、整数ベースのインデックス アクセサーを持つコレクションがあります。各スレッドは、ID から、ダーティな読み取り/書き込みを気にせずにアクセスできるインデックス値を認識できます。
不要なパフォーマンス ヒットが発生するもう 1 つのケースは、データがコレクションから読み取られるだけで、書き込まれない場合です。
消費者に任せるのが正しいアプローチだと思います。Collection インスタンスが同期されているか、別のオブジェクトが同期されているかについて、消費者にはるかに柔軟性を提供します。たとえば、両方とも更新する必要がある 2 つのリストがある場合、1 つのロックを使用して 1 つの同期ブロックにそれらを含めることが理にかなっている場合があります。
コレクション クラスを作成する場合は、スレッド セーフにしないでください。正しく行うことは非常に難しく (たとえば、正確かつ迅速に)、間違った方法で実行した場合の消費者の問題 (heisenbugs) をデバッグするのは困難です。
代わりに、コレクション API の 1 つを実装し、Collections.synchronizedCollection( yourCollectionInstance) を使用して、必要に応じてスレッドセーフな実装を取得します。
クラス javadoc で適切な Collections.synchronizedXXX メソッドを参照するだけです。設計でスレッド セーフを考慮しており、消費者が自由にスレッド セーフなオプションを使用できるようになっていることが明らかになります。
JDK 5 の時点で、スレッド セーフなコレクションが必要な場合は、java.util.concurrent で既に実装されているコレクションの 1 つが機能するかどうかを最初に確認します。Java Concurrency In Practiceの著者(ほとんどのクラスを書いた人を含む) が指摘しているように、特にパフォーマンスが重要な場合、これらを正しく実装することは非常に困難です。
引用http://download.oracle.com/javase/6/docs/api/java/util/concurrent/package-summary.html
同時収集
キューの他に、このパッケージは、マルチスレッド コンテキストで使用するために設計されたコレクションの実装を提供します: ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArrayList、および CopyOnWriteArraySet。多くのスレッドが特定のコレクションにアクセスすることが予想される場合、通常は、同期された HashMap よりも ConcurrentHashMap が優先され、同期された TreeMap よりも ConcurrentSkipListMap が通常は優先されます。CopyOnWriteArrayList は、予想される読み取りとトラバーサルの数がリストの更新の数を大幅に上回る場合、同期された ArrayList よりも適しています。