揮発性変数は、スレッドの可視性を提供し、糸の安全性と原子性を保証するものではありません。
スレッドの可視性とは何ですか:
ロックは、相互排除と可視性の2つの主な機能を提供します。相互除外とは、1つのスレッドのみが一度に特定のロックを保持できることを意味するため、この機能を使用して共有データの調整されたアクセスプロトコルを実装できるため、1つのスレッドのみが一度に共有データを使用できます。可視性はもう少し複雑であり、ロックを解放する前に共有データの変更が別のスレッドに表示されることを保証する必要があります。その後、ロックを取得することができます - 同期メカニズムによって提供されるこの視認性保証なしでは、スレッドで見られる共有変数が事前に修正または一貫性のない値を引き起こす可能性があります。
揮発性のセマンティクスを参照してください:
揮発性は、同期の弱い実装と同等です。つまり、揮発性は同期されたセマンティクスを実装しますが、ロックメカニズムはありません。揮発性フィールドへの更新が、予測可能な方法で他のスレッドに通知することを保証します。
揮発性には、次のセマンティクスが含まれています。
(1)Java Storageモデルは、勇敢な指示の操作を並べ替えません。これにより、指示が表示される順序で揮発性変数の操作が実行されることが保証されます。
(2)揮発性変数は、レジスタではキャッシュされません(スレッドのみが表示されます)またはCPUに見えない他の場所。揮発性変数の結果は、常にメインメモリから毎回読み取られます。言い換えれば、揮発性変数を変更するために、他のスレッドは常に表示され、スレッドスタック内の変数を使用していません。つまり、法律の前に、勇敢な変数を書いた後、その後の読み取り操作は、この書き込み操作の結果を確認するために理解できます。
揮発性変数の特性は良好ですが、揮発性は糸の安全性を保証することはできません。つまり、揮発性場の動作は原子ではありません。揮発性変数は可視性のみを保証できます(他のスレッドは、1つのスレッドが変更された後にこの変更を見た後に結果を理解できます)。原子性を確保するために、これまでのところロックすることしかできません!
揮発性を使用するための原則:
揮発性変数を適用するための3つの原則:
(1)書き込み変数は、この変数の値に依存しないか、この変数を1つのスレッド変更
(2)変数の状態は、他の変数との不変の制約に参加する必要はありません
(3)アクセス変数をロックする必要はありません
実際、これらの条件は、揮発性変数に書き込むことができるこれらの有効な値は、変数の現在の状態を含むあらゆるプログラムの状態とは無関係であることを示しています。
最初の条件は、揮発性変数がスレッドセーフカウンターとして使用されるのを防ぎます。増分操作(x ++)は別の操作のように見えますが、実際には、原子的に実行する必要があり、揮発性が必要な原子特性を提供することはできない一連のread-modify-write操作で構成される複合操作です。正しい操作を実装するには、操作中にxの値を一定に保つ必要がありますが、これは揮発性変数では不可能です。 (ただし、値が単一のスレッドからのみ記述されるように調整されている場合、最初の条件は無視できます。)
ほとんどのプログラミング状況は、これらの3つの条件のいずれかと矛盾しているため、同期しているように安全性に合わせて普遍的に適用できない揮発性変数があります。リスト1は、非スレッドセーフ数値範囲クラスを示しています。それには不変が含まれています - 下限は常に上限以下です。
揮発性を正しく使用してください:
モード#1:ステータスフラグ
おそらく、揮発性変数の実装の仕様は、単にブールステータスフラグを使用して、初期化の完了やダウンタイムの要求など、重要な1回限りのイベントが発生したことを示すことです。
多くのアプリケーションには、リスト2に示すように、「プログラムが停止する準備ができていないときに何らかの作業を実行する」という形の制御構造が含まれています。
リスト2。揮発性変数をステータスフラグとして使用します
揮発性ブールシャットダウンリクエスト。 …public void shutdown(){shutdownRequested = true; } public void dowork(){while(!shutdownRequested){// do suff}}}Shutdown()メソッドがループの外側から呼び出される可能性が非常に高いため、つまり別のスレッドでは、シャットダウンリケストされた変数の可視性が正しく実装されるように、何らかの同期を実行する必要があります。 (JMXリスナー、GUIイベントスレッドのオペレーションリスナー、RMIを介して、Webサービスを介して呼び出される場合があります)。ただし、同期ブロックを使用してループを書くことは、リスト2に示されている揮発性ステータスフラグで書くよりもはるかに面倒です。揮発性がエンコードを単純化するため、ステータスフラグはプログラム内の他の状態に依存しないため、ここでは揮発性に非常に適しています。
このタイプの状態タグの一般的な特徴は、通常1つの状態遷移しかないことです。シャットダウン再追跡フラグはfalseからtrueに変換され、プログラムは停止します。このパターンは、前後の遷移のステータスフラグに拡張できますが、遷移期間が認められない場合にのみ拡張できます(falseからtrueまで、次にfalseまで)。さらに、原子変数など、一部の原子状態変換メカニズムが必要です。
モード#2:1回限りの安全な出版物
同期の欠如は、達成不可能な可視性につながる可能性があるため、原始値ではなくオブジェクト参照をいつ書くかを判断することがより困難になります。同期がない場合、オブジェクト(別のスレッドによって記述された)で参照される更新された値(別のスレッド)が発生する可能性があり、そのオブジェクトの状態の古い値は同時に存在します。 (これは、オブジェクト参照が同期せずに読み取られる有名なダブルチェックロックロック問題の根本原因であり、その結果、更新された参照が表示される可能性がありますが、その参照を通じて不完全に構築されたオブジェクトが表示されます)。
オブジェクトの安全な公開を実装する1つの手法は、オブジェクト参照を揮発性タイプとして定義することです。リスト3は、スタートアップ中にバックグラウンドスレッドがデータベースからデータをロードする例を示しています。他のコードは、このデータを利用できる場合、使用前に公開されているかどうかを確認してください。
リスト3。1回限りの安全なリリースに揮発性変数を使用する
Public Class BackgroundFloobleLoader {public bolatile Floble theflooble; public void initinbackground(){//たくさんのことをしてくださいflooble = new Floble(); //これはfloobleへの唯一の書き込み}} public class someotherclass {public void dowork(){// }}}フルーブルの参照が揮発性タイプでない場合、dowork()のコードは、フルーブルを参照するときに不完全に構築されたフルーブルを取得します。
このパターンに必要な条件は、公開されたオブジェクトがスレッドセーフまたは有効な不変のオブジェクトである必要があることです(効果的な不変のオブジェクトは、オブジェクトの状態が公開後に変更されないことを意味します)。型揮発性の参照は、オブジェクトの公開フォームの可視性を確保しますが、公開後にオブジェクトの状態が変更される場合は追加の同期が必要です。
パターン#3:独立した観察
揮発性を安全に使用するもう1つの簡単なモードは、プログラムの内部使用のために定期的に観測を「リリース」することです。たとえば、周囲温度を感知できる周囲センサーがあるとします。背景スレッドは、数秒ごとにセンサーを読み取り、現在のドキュメントを含む揮発性変数を更新できます。その後、他のスレッドでは、この変数を読み取ることができ、最新の温度値をいつでも確認できます。
このモードを使用する別のアプリケーションは、プログラムの統計を収集することです。リスト4は、認証メカニズムが最後にログインしたユーザーの名前をどのように覚えているかを示しています。 LastUserリファレンスを繰り返して、プログラムの他の部分の値を公開します。
リスト4。複数の独立した観測を公開するために揮発性変数を使用する
パブリッククラスのusermanager {public volatile string lastuser; public boolean Authenticate(String User、String Password){boolean valid = passistisValid(user、password); if(valid){user u = new user(); ActiveUsers.Add(u); lastuser = user; } return valid; }}このモードは、前のモードの拡張です。プログラムの他の場所で使用するための特定の価値を公開しますが、1回限りのイベントを公開するのとは異なり、それは一連の独立したイベントです。このパターンでは、公開された値が有効で不変であること、つまり、公開後に値のステータスは変更されません。この値を使用するコードは、値がいつでも変更される可能性があることを明確にする必要があります。
モード#4:「揮発性豆」モード
揮発性の豆パターンは、Javabeansを「名誉構造」として使用するフレームワークに適しています。揮発性の豆パターンでは、Javabeansは、Getterおよび/またはSetterメソッドの独立した特性を持つ一連のコンテナとして使用されます。揮発性豆パターンの基本原理は、多くのフレームワークが揮発性データ(httpsessionなど)の保有者にコンテナを提供することですが、これらの容器に配置されたオブジェクトはスレッド安全でなければなりません。
揮発性豆モードでは、Javabeanのすべてのデータメンバーは揮発性タイプであり、ゲッターとセッターの方法は非常に普通でなければなりません - 対応するプロパティを取得または設定する以外はロジックを含めることはできません。さらに、オブジェクトによって参照されるデータメンバーの場合、参照されるオブジェクトは有効で不変でなければなりません。 (これにより、配列参照が揮発性として宣言される場合、配列自体ではなく参照のみが揮発性セマンティクスを持っているため、配列値のプロパティが禁止されます)。揮発性変数の場合、不変剤または制約はJavabeanプロパティを含めることはできません。リスト5の例は、揮発性の豆パターンを順守するJavabeansを示しています。
モード#4:「揮発性豆」モード
@threadsafeパブリッククラスの人{private volatile string firstName;プライベート揮発性文字列LastName;私的な揮発性のintage; public string getFirstName(){return firstName; } public string getLastName(){return lastName; } public int getage(){return age; } public void setFirstName(string firstName){this.firstname = firstName; } public void setLastName(string lastname){this.lastname = lastname; } public void Setage(int age){this.age = age; }}揮発性の高度なモード
前のセクションで説明したパターンは、基本的なユースケースのほとんどをカバーしており、これらのパターンで揮発性を使用することは非常に便利で単純です。このセクションでは、揮発性がパフォーマンスまたはスケーラビリティの利点を提供するより高度なモードを紹介します。
揮発性アプリケーションの高度なモードは非常に脆弱です。したがって、仮定は慎重に証明されなければなりません。これらのパターンは、非常に小さな変更でもコードを破損する可能性があるため、厳密にカプセル化されています。同様に、より高度な揮発性のユースケースを使用する理由は、パフォーマンスを改善できるため、高度なパターンの適用を開始する前にこのパフォーマンスの利点を達成する必要があることを確実に判断することです。これらのパターンにはトレードオフがあり、パフォーマンスの向上の可能性と引き換えに読みやすさまたは保守性をあきらめます。パフォーマンスの改善が必要ない場合(または厳格なテストプログラムでそれを必要とすることができない場合)、それはあなたがあきらめるものよりも少ないお金を失い、価値が少ないので悪いことになる可能性があります。
モード#5:オーバーヘッドが低い読み取りワイトロック戦略
これまでのところ、揮発性はカウンターを実装するのに十分な能力がないことを理解する必要があります。 ++ xは実際には3つの操作(読み取り、追加、保存)の単純な組み合わせであるため、複数のスレッドが揮発性カウンターで漸進的な操作を同時に実行しようとする場合、更新された値が失われる可能性があります。
ただし、読み取り操作が書き込み操作よりもはるかに多い場合は、内部ロックと揮発性変数を使用して、パブリックコードパスのオーバーヘッドを減らすことができます。リスト6に示されているスレッドセーフカウンターは、同期して使用して、増分操作が原子および揮発性であることを確認して、現在の結果の可視性を確保します。読み取りパスのオーバーヘッドには揮発性読み取り操作のみが含まれるため、この方法は頻繁に頻繁に行われない場合、より良いパフォーマンスを実現できます。
リスト6。揮発性と同期して「オーバーヘッドの低い読み取り洗浄ロック」を実現するために使用する」
@ThreadSafe Public Class Cheesycounter {//安価な読み取りワイトロックトリックを採用している//すべてのミューターティブ操作は、@guardedby( "this")プライベート不安定なint値を保持している「この」ロックを使用して行う必要があります。 public int getValue(){return値; } public synchronized int increment(){return value ++; }}この手法が「より低いオーバーヘッド読み取りワイトロック」と呼ばれる理由は、読み取りワイト操作に異なる同期メカニズムを使用するためです。この例の書き込み操作は揮発性の使用の最初の条件に違反しているため、カウンターは揮発性で安全に実装することはできません - ロックを使用する必要があります。ただし、読み取り操作で揮発性を使用して現在の値の可視性を確保することができるため、ロックを使用してすべての変更を実行し、揮発性で読み取ることができます。その中で、ロックは1つのスレッドのみが一度に値にアクセスできるようにし、揮発性により、複数のスレッドが読み取り操作を実行できます。したがって、揮発性を使用してコードパスが読み取られることを確認する場合、読み取りワイト操作のように、すべてのコードパスを実行するためにロックを使用するよりも共有されます。ただし、このモデルの弱点を念頭に置いてください。このモデルの最も基本的なアプリケーションがそれを超えている場合、これら2つの競合する同期メカニズムを組み合わせることは非常に困難になります。
指示の並べ替えと、前に行われることについて
1。再注文しましょう
Java言語の仕様は、JVMスレッドがシーケンシャルセマンティクスを内部的に維持していることを規定しています。つまり、プログラムの最終結果が厳格なシーケンシャル環境での結果と同等である限り、指示の実行順序はコードの順序と矛盾する可能性があります。このプロセスはコマンドによって並べ替えられます。命令の並べ替えの重要性は、JVMがプロセッサの特性(CPUのマルチレベルキャッシュシステム、マルチコアプロセッサなど)に従ってマシン命令を適切に並べ替えることができるため、マシンの命令はCPUの実行特性に沿って、マシンのパフォーマンスを最大化することです。
プログラム実行の最も単純なモデルは、指示を実行する命令が表示される順序で実行されることです。これは、指示を実行するCPUに依存しないため、命令の移植性を最大限に保証します。このモデルの専門用語は、順次一貫性モデルと呼ばれます。ただし、最新のコンピューターシステムとプロセッサアーキテクチャはこれを保証するものではありません(人工指定は常にCPU処理特性のコンプライアンスを保証できるとは限らないため)。
2。前方のルールの前に
Javaストレージモデルには、前に行われる原則があります。つまり、アクションBがアクションAの実行結果(A/Bが同じスレッドで実行されているかどうかに関係なく)を確認したい場合、A/Bは、実現前の関係を満たす必要があります。
実行前のルールを導入する前に、概念を紹介します:JMM Action(Java Memeory Model Action)、Javaはモデルアクションを保存します。アクションには、変数の読み取りと書き込み、ロックを監視してロックを監視し、スレッドStart()、Join()が含まれます。ロックについては後で説明します。
完全なルールの前に起こる:
(1)同じスレッド内の各アクションは、その後に表示されるアクションの前に発生します。
(2)モニターのロックを解除すると、同じモニターの後続のロックごとに発生します。
(3)同じフィールドのその後の読み取り操作ごとに、揮発性フィールドへの操作を書き込みます。
(4)スレッドの呼び出しは、スタートアップスレッドのアクションの前に発生します。
(5)スレッド内のすべてのアクションは、他のスレッドでこのスレッドを終了するか、thread.join()またはthread.isalive()== falseで戻る前に発生します。
(6)1つのスレッドAは、別のスレッドbの割り込み()を呼び出します。スレッドAがbがa(bが例外をスローするか、a b is internurded()または邪魔された()を検出するかを検出すると、bが発生する前に発生します。
(7)オブジェクトコンストラクターの終わりが発生し、オブジェクトのファイナルライザーの始まりと
(8)アクションが起こる前にアクションが行われ、Bが発生し、bが発生し、cアクションが発生した場合、アクションが発生する前にアクションが発生します。
上記はこの記事に関するすべてです。ここで紹介します。 Javaの揮発性変数を学び、理解することが役立つことを願っています。