ホームページ >Java >&#&チュートリアル >JAVA同時プログラミングの概要: スレッドセーフ、オブジェクト共有
スレッドはメモリハンドルやファイルハンドルなどのプロセス全体のリソースを共有しますが、各スレッドには独自のプログラムカウンター(プログラムカウンター)、スタック変数、およびローカル変数の待機があります。
同じプログラム内の複数のスレッドを複数の CPU で同時に実行するようにスケジュールすることもできます。
Java の主な同期メカニズムは、排他的なロック方法を提供するキーワード synchronized ですが、「同期」という用語には、揮発性タイプの変数、明示的なロック、およびアトミックも含まれます変数。
複数のスレッドが同じ変更可能な状態変数にアクセスするときに適切な同期が使用されない場合、プログラムはエラーになります。この問題を解決するには 3 つの方法があります:
スレッド間で状態変数を共有しない。
状態変数を不変変数に変更します。
状態変数にアクセスするときに同期を使用します。
スレッド セーフの定義: 複数のスレッドがクラスにアクセスする場合、このクラスは常に正しい動作を示すことができ、このクラスはスレッド セーフであると言われます。
ステートレス オブジェクトはスレッドセーフである必要があります。
ほとんどの競合状態の本質は、無効な可能性のある観察に基づいて判断を下したり、計算を実行したりすることです。このタイプの競合状態は「実行前のチェック」と呼ばれます。条件が最初に真であることが観察され (例: ファイル X が存在しない)、その後、この観察に基づいて適切なアクションが実行されます (この結果が観察されるまでの間、ファイル X は存在しません)。ファイルの作成を開始すると、監視が無効になる可能性があります (別のスレッドが作成したファイルの破損など)。
2 つの操作 A と B があるとします。A を実行しているスレッドの観点から、別のスレッドが B を実行するときに、B がすべて実行されるか、B がまったく実行されない場合、A と B は次のようになります。互いに反対であると言えます。アトミック操作とは、同じ状態にアクセスするすべての操作 (操作自体を含む) について、この操作がアトミックに実行されることを意味します。
実際の状況では、可能な限り、既存のスレッドセーフ オブジェクト (AtomicLong など) を使用してクラスの状態を管理する必要があります。非スレッドセーフ オブジェクトと比較して、スレッドセーフ オブジェクトの可能な状態とその状態遷移を判断するのが容易であり、スレッド セーフの維持と検証が容易になります。
状態の一貫性を維持するには、関連するすべての状態変数を 1 回のアトミック操作で更新する必要があります。
再入可能を実装する 1 つの方法は、取得カウントと所有者スレッドを各ロックに関連付けることです。カウント値が 0 の場合、ロックはどのスレッドによっても保持されていないと見なされます。スレッドが保持されていないロックを要求すると、JVM はロック保持者を記録し、取得カウントを 1 に設定します。同じスレッドが再度ロックを取得すると、カウンタはインクリメントされ、スレッドが同期ブロックから出ると、それに応じてカウンタもデクリメントされます。カウントが0になるとロックが解除されます。
すべてのデータをロックで保護する必要があるわけではありません。複数のスレッドによって同時にアクセスされる変数データのみをロックで保護する必要があります。
長時間の計算や、すぐに完了しない可能性のある操作 (ネットワーク IO やコンソール IO など) を実行するときは、ロックを保持しないように注意してください。
ステータスの理解を体験してください、それはクラスのメンバー変数だと思います。ステートレス オブジェクトとは、メンバー変数がデータを格納できないか、データを格納できるがデータが不変であることを意味します。ステートレス オブジェクトはスレッドセーフです。メソッドにメンバー変数がある場合は、このメンバー変数に対して関連するスレッドセーフな操作を実行する必要があります。
メソッドの前にやみくもに synchronized を追加しないでください。これにより、スレッドの安全性は確保されますが、メソッドの同時実行機能が弱くなり、同時実行をサポートできるメソッドがブロックされ、プログラムの処理速度が遅くなります。 。
synchronized で囲まれたコードはできるだけ短くする必要がありますが、影響を受けるすべてのメンバー変数はまとめて保持する必要があります。関連のないメンバー変数を複数の同期変数で囲むことができます。
ロックの意味は相互排他動作に限定されず、メモリの可視性も含みます。すべてのスレッドが共有変数の最新の値を確実に参照できるようにするには、読み取りまたは書き込み操作を実行するすべてのスレッドが同じロック上で同期されている必要があります。
Java 言語は、他のスレッドに変数の更新操作が確実に通知されるように、少し弱い同期メカニズム、つまり揮発性変数を提供します。変数が volatile として宣言されると、コンパイラーとランタイムはその変数が共有されていることを認識するため、変数に対する操作は他のメモリー操作と並べ替えられません。揮発性変数はレジスタにキャッシュされず、他のプロセッサからは見えないため、揮発性タイプの変数を読み取ると、常に最後に書き込まれた値が返されます。
volatile 変数にアクセスするときにロック操作は実行されないため、実行スレッドはブロックされません。したがって、volatile 変数は synchronized キーワードよりも軽量な同期メカニズムです。
揮発性変数は通常、操作の完了、中断、またはステータスをマークするために使用されます。 volatile のセマンティクスは、変数への書き込み操作を 1 つのスレッドだけが実行することを保証できない限り、インクリメント操作 (count++) のアトミック性を保証するのに十分ではありません。
ロックメカニズムは可視性と原子性の両方を保証できますが、揮発性変数は可視性のみを保証します。
次の条件がすべて満たされる場合にのみ、揮発性変数を使用する必要があります。
変数への書き込み操作は、変数の現在の値に依存しないか、変数の現在の値のみに依存することを保証できます。単一スレッドが変数の値を更新します。
この変数は、他の状態変数とともに不変条件には含まれません。
この変数にアクセスするときにロックは必要ありません。
オブジェクトの「公開」とは、そのオブジェクトを現在のスコープ外のコードで使用できるようにすることを意味します。
公開すべきではないオブジェクトが公開される場合、この状況をエスケープと呼びます。
施工中はこのリードを逃がさないようにしてください。
コンストラクターでイベント リスナーを登録するか、スレッドを開始する場合は、プライベート コンストラクターとパブリック ファクトリ メソッドを使用して、誤った構築を避けることができます。
スタック クロージャはスレッド クロージャの特殊なケースであり、スタック クロージャでは、ローカル変数を介してのみオブジェクトにアクセスできます。
スレッドの閉鎖を維持するためのより標準化された方法は、ThreadLocal を使用することです。このクラスは、スレッド内の特定の値を、その値を保持するオブジェクトに関連付けることができます。
ThreadLocal オブジェクトは通常、変更可能な単一インスタンス オブジェクト (シングルトン) またはグローバル変数が共有されるのを防ぐために使用されます。
以下の条件が満たされる場合、オブジェクトは不変です:
オブジェクトの作成後にその状態を変更することはできません。
オブジェクトのすべてのフィールドはfinalタイプです。
オブジェクトは正しく作成されています (この参照はオブジェクトの作成中にエスケープされませんでした)。
不変オブジェクトはスレッドセーフである必要があります。
オブジェクトを安全に公開するには、オブジェクトの参照とオブジェクトの状態が他のスレッドから同時に見える必要があります。適切に構築されたオブジェクトは、次の方法で安全に解放できます。
静的初期化関数でオブジェクト参照を初期化する。
オブジェクトの参照を volatile 型フィールドまたは AtomicReferance オブジェクトに保存します。
オブジェクトの参照を、正しく構築されたオブジェクトの最終型フィールドに保存します。
オブジェクトへの参照をロックで保護されたフィールドに保存します。
安全に公開された事実上不変オブジェクトは、追加の同期なしでどのスレッドでも安全に使用できます。
オブジェクトの公開要件は、その変更可能性に依存します:
不変オブジェクトは、どのメカニズムでも公開できます。
事実 不変オブジェクトは安全な方法で公開する必要があります。
可変オブジェクトは安全な方法で解放され、スレッドセーフであるか、ロックで保護されている必要があります。
並行プログラムでオブジェクトを使用および共有するときに使用できるいくつかの実用的な戦略があります。スレッドで囲まれたオブジェクトは 1 つのスレッドによってのみ所有でき、オブジェクトはそのスレッドに囲まれ、このスレッドによってのみ変更できます。
リリースとエスケープの理解: クラス内のメンバー変数またはオブジェクトが、static で変更された静的変数や現在呼び出しているメソッド オブジェクトなど、他のクラスから参照および使用できることを意味します。エスケープとは、複数のスレッドから参照されるべきではないメンバー変数またはオブジェクトが公開および参照され、その値が誤って変更される問題を指します。つまり、クラスや内部で使用されるメンバー変数やメソッドのスコープを拡張しないでください。これはパッケージングでも考慮すべき問題です。
このエスケープ: つまり、このオブジェクトを参照するためにコンストラクターの内部クラスで別のスレッドが開始されますが、オブジェクトはまだ構築されていないため、予期しないエラーが発生する可能性があります。解決策は、ファクトリ メソッドを作成し、コンストラクターをプライベートにすることです。
final によって変更されたメンバー変数はコンストラクターで初期化する必要があります。そうしないと、オブジェクトのインスタンス化後にメンバー変数に値を割り当てることができません。 Final で変更されたメンバ変数がオブジェクトを参照している場合、オブジェクトのアドレスは変更できませんが、オブジェクトの値は変更できます。
クラス B への参照を持つクラス A など、オブジェクトを安全に公開する 4 つの方法を理解する:
A の静的初期化メソッド (次のような public static A a = new A(b);)静的ファクトリ クラスでは、B が参照されると B が初期化されます。
クラスAのメンバー変数Bがvolatile bまたはAtomicReferance bで変更されています。
クラス A の B メンバー変数は、final B b で次のように変更されます。
クラスAのメソッドがBを使用する場合は、synchronized(lock){B...}で囲みます。
不変オブジェクトを簡単に理解すると、それらは技術的には変更可能ですが、ビジネス ロジックの処理中には変更されません。
関連する推奨事項:
以上がJAVA同時プログラミングの概要: スレッドセーフ、オブジェクト共有の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。