Javaのvolatileキーワード第一原理の発生と詳細

volatileキーワードは、Java仮想マシンが提供する最も軽量な同期メカニズムであると言うことができるが、それは多くのプログラマがそれを使用しないように慣れていることを完全に正しいと完全に理解することは容易ではなく、複数のスレッドに対処するために会いましたデータの競争の問題のすべては、同期のための同期使用。マルチスレッド操作の意味の理解の他の特性にvolatile変数の意味を理解し、この記事では、私たちはイエス最後に揮発性のものを意味を説明します。Javaのメモリモデル(Javaのメモリモデル、JMM)中の揮発性キーワードに、より関連性を持っているので、volatileキーワードを導入する前に、我々は最初のJavaメモリモデルをご紹介します。

Javaのメモリモデルは、を参照してくださいJavaのメモリモデル

 

後に導入された8つのメモリ・アクセス操作と限ら上記の規則に加え、揮発性のためにいくつかの特別な規定が、並行Javaプログラムでメモリアクセス動作が安全である完全に決定されています。これは非常に厳しいが、非常に面倒で定義されているので、多くの問題を練習。したがって、以下では、判定原則と同等の定義を説明 - 同時アクセス環境を決定するための第一の原則を発生し、安全です。

1.第一の原則が発生します

Java言語は、「最初に出現する」の原則を持っている(事前発生)A。この原則は、この原則に頼る主にスレッドセーフかどうかに基づいて、競合データは、存在するかどうかを決定することである、非常に重要であり、我々は競合の可能性は、いくつかの規則によって2つの同時動作環境の間に存在するすべての問題を解決することができます。

今すぐ「最初の発生」の原則を見て、それが何であるかを参照ください。動作は、第1の動作Bで発生した場合、観察された生成動作Bが発生する前に、動作はA、Bの動作に影響を与えることができることを、実際には、Java(登録商標)メモリ・モデルで定義された2つの操作の間の第1の部分的順序関係を発生「効果」メソッドを呼び出し、メッセージを送信し、メモリ内の共有変数の値を変更することが含まれます。この文は理解することは難しいことではありませんが、それは何を意味するのでしょうか?我々は、コードに示すように、この3つの擬似コードを説明するために例を与えることができます。

// 以下の操作は、スレッドAで実行され、 
K = 1 ;
 // 次の操作は、スレッドBの中で行われる 
J = K;
 // 次の操作がスレッドCで実行され、 
K = 2。

スレッドは、スレッドBが動作を実行した後に、それを決定することができる、に基づいて、操作「K = 1」は「J = K」スレッドBの動作に先立って生じることが想定され、変数jの値は、この結論は有し、定数に等しいです。 2:まず、結果の原理は、まず、「k = 1」を観察することができる起こる、2つのスレッド・Cが「ステージ」ではない、他のスレッドは、スレッド操作の後、変数kの値を変更しないであろう。私たちは今、スレッドCを考えてみましょう、我々はまだ最初のスレッドAとスレッドBとの間の関係を維持し、そしてCは、オペレーティング・スレッドAとスレッドB間のスレッドに表示され、スレッドB及びCが、スレッドが最初に発生した問題ではありません、それはJです値はどのくらいですか?答えは不確実です!変数kスレッドCの影響の古いデータを読み出すために、そのスレッドBを観察し、または、この時点でリスクがあるとしないことがあり、スレッドBことができるので、1及び2は、可能性のある、マルチスレッドセーフ持っていません。

 

ここではいくつかの「自然」の関係は、最初のJavaメモリモデル、これらの事前あなたは、コード内で直接使用することができますシンクロナイザが既に存在するどのような援助なしに関係の下で起こっています。二つの操作間の関係が表示されていない、そして次のルールから導き出すことができない場合、彼らは保護のない秩序を持っていない、仮想マシンは、自由に並べ替えることができます。

  • ルールのプログラムシーケンス(プログラム順ルール):スレッドで、コードブックEDITORIAL書き込み動作のプログラム順序によれば最初の操作後に発生します。むしろ、分岐、ループ、および他の構造として考えられるべきで、制御フローシーケンス順ではなく、プログラムコードであるべきです。
  • チューブロックルール(モニターロックルール):ロック解除操作が発生した後の顔にロック操作のロックを推進。ここでは、同じロック、および「裏」は、時間の順序を参照することを強調しなければなりません。
  • 揮発性変数ルール(volatile変数ルール):揮発性変数への書き込みが最初にも時間的順序を意味する「後ろ」読み出し動作の後、この変数の面で発生しました。
  • スレッド開始規則(スレッド開始ルール):このスレッドでThreadオブジェクト()メソッドが最初に出現ごとに移動を開始。
  • スレッドには、ルール(スレッドの終了ルール)を終了する:すべての操作は、このスレッドの検出には、我々はThread.join()メソッド、Thread.isAlive()の戻り値と他の手段で終了することができます発生先読みスレッドの終了のあるスレッドが検出されました終了しました。
  • スレッドは、(中断ルールスレッド)のルールを破る:呼び出し元のスレッドの割り込み()メソッドが最初に発生するイベントを中断し、中断スレッドコード検出で発生した割り込みが発生した場合、Thread.interrupted()メソッドによって検出することができます。
  • ルールオブジェクトの端(ファイナライザルール):オブジェクトの初期化の完了は(コンストラクタが終了を実行する)最初に起動するそのファイナライズ()メソッドで発生しました。
  • 推移(推移):動作が先に発生した場合手順B、B先読み操作は手順Cに発生、動作Aは、Cが先に操作が発生していると結論付けることができます。

同期は、任意の事前せずに発生したJava言語のルールは、上記のものを保護するための手段を設定することができるよう、以下の読み取りと書き込みの操作のための順序と相互に運用するかどうかを判断するためにこれらのルールを使用する方法を紹介します変数を共有し、それがスレッドセーフかどうかであります読者はまた、「時間順」との間の「最初の発生」について異なるもの以下の例から感じることができます。

プライベート int型の値= 0 ; 
pubilc ボイドのsetValue(int型の値){ 
     この .VALUE = 値。
} 
公共 INT のgetValue(){ 
     戻り値。
}

上記のコードは、(連続時間)最初のスレッド、共通のセットが、ゲッター/セッターメソッド、スレッドA及びBの存在が想定されている「のsetValue(1)」と呼んで、スレッドB」は、同じオブジェクトを呼び出しますgetValue()」、そして、スレッドBの戻り値は何である受信しましたか?

答えは不明です。

なぜ?

次は、ルールの原則が原因スレッドAとスレッドB、いないスレッドによって呼び出される2つの方法に、最初に発生した分析するために回し、そのプログラムシーケンス規則は、ここでは適用されません。シンクブロックの不在、自然ではありませんチューブロックルールが適用されないようにロック及びアンロック操作は、発生する;揮発性キーワード値変数が変更されないので、それほど揮発性変数ルールが適用されない、糸の背後には、開始終了し、ルールおよび終了ルールをオブジェクトとがあります関係。ルールの最初の発生は適用されませんので、最後のパスは論外もあるので、我々はBをスレッド前に動作時間のスレッドが決定することができますが、スレッドBを決定することはできませんので、「のgetValue()」メソッドが結果を返します言い換えれば、スレッドセーフな操作はありません。

 

それはこの問題を解決する方法は?

私たちは、少なくとも2つの比較的シンプルなソリューションを選択することができますがあります。

  1. ルールをロックチューブを適用することができるように、いずれかのゲッター/セッターメソッドは、同期メソッドとして定義されます。
  2. どちらの値が原因volatileキーワードの使用シナリオを満たすために、元の値の値に依存しない値設定方法の変更に、揮発性の変数として定義されているので、あなたが最初に発生した変数揮発性の関係を達成するためにルールを適用することができます。

上記の例では、我々はそれを締結することができます。アクションは、この操作は、「最初の発生」になることを意味しない「時間に最初の発生」、およびオペレーション場合、「最初に出現するが、」この操作は「時間でなければならないかどうかを推測することができること最初の意味」で発生?残念ながら、この推論が確立されていない証明する次のコード例に示されるように、典型的な例は、「並べ替え命令」を数回言及されています

// 以下の操作は、同じスレッドで実行される
のint = I 1 INT J = 2。

2つの割り当てのリストは、操作「I = 1 INT」が、「int型J = 2」のコードがよく最初であってもよいし、プログラムシーケンス規則に従って、同じスレッド内の最初の「int型J = 2」で発生します私たちは、このスレッドではこの点を認識することができないため、プロセッサ、これは、最初に起こったの原則の有効性には影響を与えません。

上記の2つの例は、結論を証明するために一緒になって:第一の原則との間に基本的に有意な関係を年代順に発生しましたので、私たちは、安全性の問題が年代順に干渉を受けない並行性を測定するとき、すべてが最初に出現したの原則に従わなければなりません。

 

2.詳細な揮発性

2.1。揮発性特性

揮発性のためのJavaのメモリモデルは、具体的には、いくつかの特別なアクセスルールを定義する変数をvolatileとして定義されている場合、それは2つのプロパティを持つことになります。

  1. この変数は、すべてのスレッドの視認性を確保するために、スレッドはこの変数の値を変更する場合、つまり、他のスレッドのための新しい値はすぐに学習します。通常の変数はこれを行うことができない、スレッド間で渡される共通の変数の値は、例えば、メインメモリを完了するために必要とされる、共通のスレッド変数の修正値、及び、メインメモリに書き戻す、スレッドBに追加のスレッド完了後、再びメインメモリからの読み出し動作となっている、変数の新しい値は、Bをスレッドに表示されます
  2. 命令並べ替え、最適化を禁止します一般的な変数は、すべてが正しい結果にメソッドの実行中に取得することができる場所の割り当ての結果に依存していることを確認しますが、順序および変数割り当てでのプログラムコードの一貫性の実現を保証することはできません。方法スレッドの実行中(内スレッドAS-IF-に記載のJavaメモリ・モデルは、いわゆる「シリアルセマンティクス用雌ねじ性能」であり、この点を認識できないので、

2.2。揮発性アトミックにそれを保証することができますか?

これは、同時、安全でないとして計算中の揮発性の変数につながる、揮発性の原子性を保証するものではありません。

以下のような:複数のスレッドの下でインクリメント演算子

パブリック クラスVolatileTest { 
    
    公共の 静的な 揮発性の int型のレース= 0 ; 
 
    プライベート 静的 最終 int型 = 20 THREADS_COUNT 
    
    パブリック 静的 ボイド増加(){ 
        レース ++ ; 
    } 
 
    パブリック 静的 ボイドメイン(文字列[]引数)がスローInterruptedExceptionある{ 
        スレッド[]スレッド = 新しいスレッド[THREADS_COUNT]。
        以下のためにINTは、I = 0; iはTHREADS_COUNT <; iは++ ){ 
            スレッド[I]= 新しいスレッド(新しいRunnableを(){ 
                @Override 
                公共 ボイドラン(){
                     ためint型私は++; I <10000は、I = 0 ){ 
                        ;増加し()
                    } 
                } 
            })。
            スレッド[I] .start(); 
        } 
 
        一方(Thread.activeCount()> 1 ){ 
            Thread.yield()。
        } 
        のSystem.out.println(レース)
    } 
}
コードの表示

このコードは、IDEA無限ループの下で実行された場合は、特定の理由を見ることができ、DEBUGを実行することができます使用:インタビューはCASを聞いてきます、あなたは理解できますか?

分析の例:

このコードは、コードが同時修正できるかどうか、最終的な出力結果200,000である必要があり、20スレッド、レース変数万インクリメント演算子のそれぞれを開始しました。このコードを実行した後の結果、および希望を達成することはありません、あなたがプログラムを実行するたびに、結果の出力が同じではありませんでしょうが、200,000未満の数である、これはなぜですか?

問題は、インクリメント演算子で発生する「レース++」コードデコンパイルの間、我々は、コードの一行だけ()メソッドのてjavapの増加を見つけるバイトから、クラスのドキュメントで構成され、4バイトコード命令であります揮発性のキーワードは、この時点でのレースの値が正しいことを確認したときに、スタック操作のトップを取るために、レースの値のgetstaticコマンドが、実行iconst_1でで、これらをIADD:コードレベルでは、同時の失敗の理由を分析することは容易です命令、他のスレッドは、操作の値でスタックのトップが古いデータとなるが、レースの値が増加していることができるので、putstatic命令実行後にメインメモリに再び同期小さいレースを評価することができる場合。

GetStatic // 静的変数のレースを取得し、スタックへの値 
iconst_1で   // int型の値1をスタックにプッシュされ 
IADD       // スタックにつながる2つのint型の値をスタックにし、ほかの 
putstatic // されます静的変数のレースの割り当て

この例から、我々は判断できアトミック保証することはできません揮発性の原子の一部が動作クラスは、java.util.concurrent.atomicパッケージに使用できることを保証するために、アトミック操作を。最も一般的な例:のAtomicInteger。

 

2.3。揮発性、それの秩序を確保するには?

コマンド並べ替えを無効にすることができ、揮発性揮発性キーワードに上記特性のため、揮発性ある程度秩序を確保します。

でブログを参照してくださいシングルトン達成するために、二重検知機構部品を。

 

2.4。揮発性使用制限

 

揮発性物質の変数にのみ可視性を保証することができ、操作シーンでは、次の2つの規則に準拠していない、我々はまだロックを渡す必要が(またはjava.util.concurrentのアトミックなクラスを使用して同期)原子性を確保します。

操作の結果は、変数の現在の値に依存しない、または単一のスレッドのみが変数の値を変更することを保証します。
変数は、他の状態変数との不変式に参加する必要はありません。

2.5。揮発性の利用シナリオ

2.5.1。ステータスフラグ量

したがって、すべてのスレッドが標識の量の最新の状態にリアルタイムでアクセスできることを保証し、すべてのスレッドがリアルタイムで表示されるために標識の量を述べた標識の量の状態を変更するには、さらに揮発使用して動作するかどうかを決定します。揮発性によって変更することができるような一般的なプロモーション活動「スパイク」は、正しく取り扱い商品が販売するかどうかを、並行性を確保するために、フィールドを「完売れます」。

揮発性の ブールフラグ= ;
一方、(!フラグ){ 
    doSomethingの()。
} 
公共 ボイドSETFLAG(){ 
    フラグ = 
}

シングルトン実装2.5.2二重検出機構

極端な場合には、通常の二重検出機構は、問題を並べ替える命令が実装シングルトンを補正することができる命令の並べ替えを禁止し、インスタンスを変更するために揮発性の使用を介して起こります。

パブリック クラスシングルトン{
     // 民営コンストラクタ
    プライベートシングルトン(){ 
    } 
    // 揮発性修飾シングルトンオブジェクト
    プライベート 静的 揮発性のシングルトンインスタンス= NULL ; 
 
    // 外部によって提供されるファクトリメソッド
    のパブリック 静的シングルトンのgetInstance(){
         IF(例えば== NULL){ // 最初に検出された
            同期(シングルトン。クラス){     // 同期ロック
                IF(例えば== NULL){ // 第二のテスト 
                    =インスタンスを新しい新しいシングルトン(); // 初期化
                } 
            } 
        } 
        戻りインスタンス; 
    } 
}

 

3.概要:

  1. 各スレッドは、独自のワーキングメモリ、ワーキングメモリを有し、同時場合、メンバ変数kの値を変更してもよいスレッドようにデータは、バックメインメモリにリアルタイムで更新されず、Bを読み取り、スレッドができませんワーキングメモリのスレッドAがメインメモリにフラッシュバックされていないため、スレッドの修正値は、スレッドBは、最新の値を読み取ることができない原因となります。
  2. ワーキングメモリ内のメインメモリの開始前に、最新の値を更新する必要があり、揮発性の変数現在のスレッドが行っている揮発性の変数に変更された値、他のスレッドを見ることができることを保証し、それぞれ使用。
  3. ワーキングメモリでは、各変更は揮発性変数がすぐに戻って他のスレッドが自分の揮発性変数の変更が行われている見ることができることを保証し、メインメモリに同期させなければならないと宣言しました。
  4. 揮発性の変数は、プログラムコードの同じ実行順序を保証するために、並べ替えの指示に最適化されていません。
  5. 揮発性、視認性を確保するため、(保証のみ揮発性変数)を確保するために一部を発注、原子性を保証するものではありません。
  6. 命令並べ替えの目的は、性能が、命令は実行のみのシングルスレッドで最終結果を変更しないことが保証並べ替え向上させることであるが、マルチスレッドで結果を保証することはできません。
  7. 揮発性メモリのセマンティクスを達成するために、バイトコードを生成するコンパイラが順序変更を禁止する命令シーケンスメモリバリアに挿入されます。

 

 

参考:

1. Javaの並行処理:揮発コメント

2. Javaの並行性:揮発性のキーワード分析は、(個人的な感情は非常に明確です)

 

おすすめ

転載: www.cnblogs.com/gjmhome/p/11413696.html