Java コードを常にスレッドセーフにするべきでしょうか、それともパフォーマンス上の理由から必要な場合にのみ行うべきでしょうか?

StackOverflow https://stackoverflow.com/questions/234341

質問

現時点では単一スレッドでのみ使用されるクラスを作成する場合、現時点では必要ない場合でも、クラスをスレッドセーフにする必要がありますか?後でこのクラスを複数のスレッドで使用することが起こり得ます。そのとき、最初からクラスをスレッドセーフにしなかった場合、競合状態が発生する可能性があり、競合状態を見つけるのに苦労する可能性があります。それとも、パフォーマンスを向上させるために、クラスをスレッドセーフにしない方がよいでしょうか?しかし、時期尚早な最適化は悪です。

別の質問:必要に応じてクラスをスレッドセーフにする必要がありますか (複数のスレッドで使用する場合、そうでない場合はそうでない場合)、それとも必要に応じてこの問題を最適化する必要がありますか (同期が処理時間の重要な部分を消費していることがわかった場合)。

どちらかの方法を選択した場合、デメリットを軽減する方法はありますか?それとも、使用すべき 3 番目の可能性が存在しますか?

編集:この疑問が私の頭に浮かんだ理由を述べます。私たちの会社では、データをプロパティ ファイルに書き込む非常に単純なユーザー管理を作成しました。Webアプリでそれを使用しましたが、いくつかの作業を行った後、ユーザー管理がユーザーのプロパティ(名前とパスワードを含む)とロールを忘れていたという奇妙なエラーが発生しました。これは非常に面倒でしたが、一貫して再現できなかったので、競合状態だったと思います。ディスクに対する読み取りと書き込みのすべてのメソッドを同期したため、問題はなくなりました。そこで私は、最初から同期を使用してクラスを作成していれば、おそらくすべての面倒な作業を回避できたのではないかと考えました。

編集2:Pragmatic Programmer のヒントを見ていると、ヒント #41 を見つけました。常に同時実行を考慮して設計します。これは、すべてのコードがスレッドセーフであるべきだというわけではありませんが、同時実行性を念頭に置いて設計する必要があるということです。

役に立ちましたか?

解決

データから開始します。明示的に共有するデータを決定し、保護します。可能な限り、ロックをデータでカプセル化します。既存のスレッドセーフコンカレントコレクションを使用します。

可能な限り、不変オブジェクトを使用します。属性を最終的に設定し、コンストラクタで値を設定します。 「変更」する必要がある場合データは新しいインスタンスを返すことを検討しています。不変オブジェクトにはロックは必要ありません。

共有またはスレッド制限されていないオブジェクトについては、スレッドセーフにするために時間を費やさないでください。

コードに期待を記録します。 JCIPアノテーションは、利用可能な事前定義済みの最良の選択肢です。

他のヒント

以前はすべてをスレッドセーフにしようとしていましたが、「スレッドセーフ」の意味そのものが使用法に依存します。多くの場合、その使用法を予測することはできず、呼び出し元はスレッドセーフな方法で使用するためにとにかくアクションを起こす必要があります。

最近は、シングルスレッドを想定してほとんどすべてを書いており、スレッドに関する知識を重要ないくつかの場所に置いています。

とは言っても、私は(適切な場合)不変型も作成します。これは当然、マルチスレッドに適しています-また、一般的に推論するのが簡単です。

「できる限り単純だが単純ではない」の原則に従う。要件がない場合は、スレッドセーフにしないでください。そうすることは投機的であり、おそらく不要です。スレッドセーフプログラミングは、クラスをより複雑にし、同期タスクのためにパフォーマンスが低下する可能性があります。

オブジェクトがスレッドセーフであると明示的に述べられていない限り、そうではないと期待されます。

個人的には、「スレッドセーフ」なクラスのみを設計します。必要な場合-必要な場合にのみ最適化の原則に基づきます。 Sunは、シングルスレッドコレクションクラスの例でも同じように進んでいるようです。

ただし、変更する場合はどちらの方法でも役立ついくつかの良い原則があります:

  1. 最も重要なこと:同期する前に考えてください。かつてスタッフを同期させていた同僚がいましたが、「万が一の場合-同期がすべて良くなったはずですよね?」これは間違っており、複数のデッドロックバグの原因でした。
  2. オブジェクトを不変にすることができる場合は、不変にします。これはスレッド化に役立つだけでなく、マップなどのキーとしてセットで安全に使用できるようになります
  3. オブジェクトをできるだけシンプルにします。それぞれが理想的には1つのジョブを実行する必要があります。半分のメンバーへのアクセスを同期する必要がある場合は、オブジェクトを2つに分割する必要があります。
  4. java.util.concurrentを学習し、可能な限りそれを使用します。それらのコードは、99%のケースであなた(または私のもの)よりも良く、速く、安全です。
  5. Javaでの同時プログラミングを読んでください。素晴らしい!

単なる副次的発言として:同期!=スレッドセーフ。それでも、データを同時に変更することはできませんが、同時に読み取ることができます。したがって、同期とは、データの同時変更を保護するだけでなく、すべてのスレッドでデータの信頼性を確保することを意味するJavaメモリモデルを念頭に置いてください。

はい、私の意見では、スレッドセーフは最初から組み込まれている必要があり、並行性の処理が必要な場合はアプリケーションロジックに依存します。何も仮定しないでください。テストがうまくいくように見えても、競合状態は眠っている犬です。

JCIP アノテーションは、どのクラスがスレッドセーフであるかを宣言するのに非常に役立つことがわかりました。私のチームは、 @ThreadSafe としてクラスに注釈を付けます。< a href = "http://www.javaconcurrencyinpractice.com/annotations/doc/net/jcip/annotations/NotThreadSafe.html" rel = "nofollow noreferrer"> @ NotThreadSafe または @ Immutable 。これは、Javadocを読むよりもはるかに明確であり、 FindBugs は、@ Immutableおよび @ GuardedBy 契約も。

コードのどのセグメントがマルチスレッド化され、どのセグメントがマルチスレッド化されないかを完全に知る必要があります。

マルチスレッドの領域を小さく制御可能なセクションに集中できなければ、成功しません。アプリのマルチスレッド化された部分は、慎重に検討し、完全に分析し、理解し、マルチスレッド環境に適応させる必要があります。

残りはそうではないため、スレッドセーフにするのは無駄です。

たとえば、swing GUIでは、Sunはマルチスレッド化しないと判断しました。

ああ、もし誰かがあなたのクラスを使うなら、それがスレッド化されたセクションにあるならそれをスレッドセーフにすることを保証するのは彼ら次第です

Sunは当初、スレッドセーフコレクション(のみ)を発表しました。問題は、スレッドセーフを非スレッドセーフにすることができないことです(パフォーマンスの目的で)。そこで、ラッパーを使用してスレッドセーフにしないスレッドセーフでないバージョンを開発しました。ほとんどの場合、ラッパーは不要です-自分でスレッドを作成する場合を除き、クラスはスレッドセーフである必要はありませんが、javadocsでドキュメント化してください。

私の個人的なアプローチは次のとおりです。

  • どこでもオブジェクトとデータ構造を不変にします。これは一般的に良い習慣であり、自動的にスレッドセーフです。問題は解決しました。
  • オブジェクトを可変にする必要がある場合、通常はスレッドセーフにしようとしないでください。この理由は簡単です。変更可能な状態にある場合、ロック/制御は単一のクラスで安全に処理できません。すべてのメソッドを同期しても、スレッドの安全性は保証されません。また、シングルスレッドコンテキストでのみ使用されるオブジェクトに同期を追加すると、不要なオーバーヘッドが追加されます。したがって、必要なロックシステムを実装するために、呼び出し元/ユーザーに任せることもできます。
  • より高いレベルのパブリックAPIを提供する場合、 APIスレッドを安全にするために必要なロックを実装します。より高いレベルの機能については、スレッドセーフのオーバーヘッドは非常に簡単であり、ユーザーは間違いなく感謝します。ユーザーが回避する必要がある複雑な同時実行セマンティクスを備えたAPIは、良いAPIではありません!

このアプローチは長い間私に役立ってきました:時々例外を作る必要があるかもしれませんが、平均して始めるのに非常に良い場所です!

SunがJava APIで行ったことをフォローしたい場合は、コレクションクラスをご覧ください。一般的なコレクションクラスの多くはスレッドセーフではありませんが、スレッドセーフなクラスを持っています。 Jon Skeet(コメントを参照)によると、Javaクラスの多くはもともとスレッドセーフでしたが、開発者にとっては有益ではなかったため、一部のクラスには2つのバージョンがあります-1つはスレッドセーフで、もう1つはスレッドセーフではありません。

スレッドセーフに関連するオーバーヘッドがあるため、必要になるまでコードをスレッドセーフにしないことをお勧めします。これは最適化と同じカテゴリに分類されると思います。必要になる前に実行しないでください。

複数のスレッドから使​​用するクラスを個別に設計し、単一スレッドのみから使用する他のクラスを文書化します。

シングルスレッドの方がはるかに作業が簡単です。

マルチスレッド ロジックを分離すると、同期を正しく行うことができます。

&quot;常に&quot;ソフトウェア開発において非常に危険な言葉です...このような選択は「常に」です。状況的。

競合状態を回避するには、1つのオブジェクトのみをロックします-競合状態の説明を面倒に読むと、クロスロック(競合状態は誤称です-競合はそこで停止します)は常に2つ以上のスレッドの試行の結果であることがわかります2つのオブジェクトをロックします。

すべてのメソッドを同期させてテストを行います-同期の問題に実際に対処しなければならない実際のアプリの場合、わずかなコストです。彼らがあなたに言っていないことは、全体が16ビットのポインタテーブルでロックアウトするということです...その時点であなたはええと、...

バーガーフリッピン履歴書を常に最新の状態に保ちます。

  

クラスを作成する場合、現時点では単一のスレッドでのみ使用されますが、スレッドセーフにする必要があります

プログラム全体がスレッドセーフであるためには、スレッドが使用するクラス自体がスレッドセーフである必要はありません。 「スレッドセーフ」以外のオブジェクトを安全に共有できます。スレッド間のクラス場合、適切な同期によって保護されている場合。したがって、明らかになるまでクラス自体をスレッドセーフにする必要はありません。

ただし、マルチスレッドはプログラムの基本的な(アーキテクチャ上の)選択です。 実際に後から追加するものではありません。そのため、スレッドセーフにする必要があるクラスを最初から知っておく必要があります。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top