Javaの並行処理ガイド5:JMMの最終キーワード分析

前述のロックおよび揮発性と比較すると、通常の変数アクセスのような、最終的なフィールドを読み書きします。最後のドメインの場合、コンパイラやプロセッサは、2つの並べ替えルールに準拠します

  1. 最後のフィールドのコンストラクタに書き込まれ、その後、オブジェクトは、これら二つの動作の間に再配列しない、参照変数に割り当てられたこの構成を参照。
  2. 最終の参照を含む第一の標的ドメインを読み取り、次いで、最終的な最初のフィールドを読み取り、並べ替えは、これら2つの操作の間はありません。

以下では、これら2つのルールを説明するために、いくつかのサンプルコードを渡します。

public class FinalExample {
    int i;                            //普通变量
    final int j;                      //final变量
    static FinalExample obj;

    public void FinalExample () {     //构造函数
        i = 1;                        //写普通域
        j = 2;                        //写final域
    }

    public static void writer () {    //写线程A执行
        obj = new FinalExample ();
    }

    public static void reader () {       //读线程B执行
        FinalExample object = obj;       //读对象引用
        int a = object.i;                //读普通域
        int b = object.j;                //读final域
    }
}

 

 

別のスレッドBが続くライタースレッドAの実行()メソッドは、リーダー()メソッドを実行することを想定しています。我々の下にはこれら2つのルールを説明するために、これらの2つのスレッドを介して相互作用。

並べ替えルールに、最終的なドメインを書きます

書かれた最後のフィールドの並べ替え規則は、コンストラクタの外に最後のフィールドを並べ替えの書き込みを禁止しています。この実装ルールには、次の2つの側面が含まれています。

  • JMMは、コンストラクタの外に最後のフィールドを並べ替え書き込むようにコンパイラを禁止しています。
  • コンパイラは最後のフィールドの後に書きます前に、コンストラクタはStoreStore障壁を挿入し、返します。この障壁は、コンストラクタの外に最後のフィールドを並べ替え書き込むためのプロセッサを禁止されています。

私たちは今、作家()メソッドを分析してみましょう。finalExampleは=新しいFinalExample():ライター()メソッドは、コードの唯一の行を含みます。このコードは、2つのステップを含みます。

  1. オブジェクト型構築FinalExample。
  2. 参照変数objに参照オブジェクトの割り当て。

スレッドBは、以下の、(すぐになぜこの仮定を説明する)は、オブジェクト参照の並べ替えを負いませんし、ドメインオブジェクトのメンバーの間で読ま読み込み可能な実行順序であります:

 

図では、書き込み動作共通ドメインが推奨され、スレッドBの外側コンストラクタをコンパイルするために命じ変数iの初期化の前に、普通誤った読みの値を読み出します。書き込まれた最後のフィールドの動作は、最終的な照合ドメインを再度書き込まれるコンストラクタ内で、スレッドBは、最終的な変数の初期化後に正しい読取り値を読み出し、「定義されています」。

書かれた最後のフィールドの並べ替えルールのことを確認するには:任意のスレッドを基準として見ることができる前に、通常のドメインが対象で、この保証はありませんが、最後のドメインオブジェクトが既に、正しく初期化されています。一例として、リーダースレッドBにオブジェクト参照OBJ、おそらくまだ構築されていないオブジェクトobjは(iは共通のドメインの操作を記述する場合の初期値は2まだ書き込まれていないコンストラクタに並べ替えする「見る」の上図一般的なドメインI)。

ドメインを並べ替え最終規則を読みます

次のように読み取りフィールドの順序を変更することは、最終的なルールは以下のとおりです。

  • 1つのスレッドで、最終フィールドの読み出した初期読み出し一次オブジェクト参照は、プロセッサが2つの操作を並べ替えJMM禁止されて、オブジェクトが含まれている(なおだけプロセッサは、このルール)。LoadLoadコンパイラは、最終的な読み取りドメイン操作の前にバリアを挿入します。

最初の読み取りと最終的なオブジェクト参照フィールドを読み取る最初のオブジェクト、二つの操作の間に間接的な依存関係を含んでいます。コンパイラー準拠間接的な依存関係するので、コンパイラは、これら二つの動作の並べ替えを行います。ほとんどのプロセッサは、ほとんどのプロセッサは、これら2つの操作の順序を変更しませんが、間接的に依存遵守します。しかし、プロセッサの数は、オペレータが(例えばアルファプロセッサのような)並び替え間接的依存を行うことができ、このルールは、そのようなプロセッサのために設計されています。

リーダー()メソッドは、3つの操作が含まれています。

  1. まず、参照変数OBJを読みます。
  2. オブジェクトに通常のドメイン変数j OBJポイントを読み込むための初期参照。
  3. まず私は、参照変数ポインティングオブジェクトOBJ finalフィールドをお読みください。

今、私たちは発生プロセッサに間接的に依存して、プログラムの実行の不遵守で、次が可能な実行順序である一方、ライター・スレッドAは、任意の並べ替えを持っていないことを前提としています。

 

オブジェクトのプロセッサリオーダー参照によって通常のオブジェクトドメインの図では、読み出し動作の読み出しの前に。スレッドAが書かれて書かれていない通常のドメインを、読んで、これは間違った読み出し動作です。最終的なフィールドが最後のフィールドが正しい読み出し動作であるスレッドAを、初期化された時点でオブジェクト参照を、読んだ後に「定義」読み取り、操作対象の最終フィールドを並べ替え規則を読み出します。

対象ドメインの最後の読み取りの前に、我々は最初の最後のフィールドは、オブジェクトの参照が含まれて読んでますことを保証するために、最後のフィールドの並べ替えルールを読みます。参照がnullでない場合は、このサンプル・プログラムでは、そのオブジェクトの参照最終ドメインは、前のスレッドを初期化されている必要があります。

最後のフィールドは、参照型である場合

我々は最後のフィールドは、基礎となるデータ型で、最後のフィールドが参照型である場合を見てみましょう参照の上、どのような効果があるのだろうか?

次のサンプルコードを考えてみましょう。

public class FinalReferenceExample {
final int[] intArray;                     //final是引用类型
static FinalReferenceExample obj;

public FinalReferenceExample () {        //构造函数
    intArray = new int[1];              //1
    intArray[0] = 1;                   //2
}

public static void writerOne () {          //写线程A执行
    obj = new FinalReferenceExample ();  //3
}

public static void writerTwo () {          //写线程B执行
    obj.intArray[0] = 2;                 //4
}

public static void reader () {              //读线程C执行
    if (obj != null) {                    //5
        int temp1 = obj.intArray[0];       //6
    }
}
}

ここで最後のフィールドは、int型の配列オブジェクトを参照する参照型です。参照型の場合は、書き込み磁界最終並べ替えルールコンパイラとプロセッサは、次の制約を追加します。

  1. ドメインの最終的な基準部材のオブジェクトのコンストラクタ関数で記述され、そしてこれは、これらの二つの操作の間に再配列しない、参照変数に割り当てられたオブジェクトのコンストラクタ関数外部構成で参照されています。

例えば上記の手順は、我々は、スレッドBがwriterTwoを実行した後、最初のスレッドA実行writerOne()メソッドが実行されると仮定する()メソッドは、スレッドCの後に実行されるリーダー()メソッドを実行します。ここでは一つの可能​​なスレッドの実行タイミングは次のとおりです。

 

図において、1は最後のフィールドに書き込まれ、2が参照ドメインオブジェクトのドメインの最後のメンバーに書き込まれる、図3は、オブジェクト参照の構造は、参照変数に割り当てられています。ここで図1及び3に加えて図2及び図3を並べ替えることができない、前述の順序を変更することができません。

JMMは、リーダスレッドCは、少なくともドメインのメンバーは、最終のコンストラクタ内のオブジェクトへの参照をスレッド書く見ることができることを確実にします。少なくとも配列に1標識されている参照するに、すなわち0 C。アレイエレメントへの書き込みスレッドBが書き込まれ、リーダスレッドが表示されないことがあり、Cに見えるかもしれません。JMMは、このときの結果は予測不可能であり、読み出されたデータとの間の競合が存在する目に見えるので、書き込みスレッドBはスレッドCを読み取り、スレッドBはスレッドCを記述することが保証されません。

あなたが読み取り、書き込み、スレッドBスレッドCは、書くために配列の要素を参照してくださいことを確認するには、メモリはスレッドの可視性を読むことを確実にするために、スレッドBとCの間で同期プリミティブ(ロックまたは揮発性)を使用する必要があります。

なぜ最後の引用は、コンストラクタ内部から「脱出」することはできません

先ほど述べたように、書き込まれた最後のフィールドの並べ替えルールのことを確認する:任意のスレッドが正しくコンストラクタで初期化されている最終の基準可変ドメインオブジェクトを指す参照変数に表示される前に。実際には、この効果を得るために、我々はまた、保証を必要とする:コンストラクタ内のオブジェクト参照がコンストラクタで「エスケープ」することができない、つまり、これは他のスレッドから見える構築されたオブジェクトへの参照であるとすることはできません。問題を説明するために、以下のサンプルコードを見てみましょう:

public class FinalReferenceEscapeExample {
final int i;
static FinalReferenceEscapeExample obj;

public FinalReferenceEscapeExample () {
    i = 1;                              //1写final域
    obj = this;                          //2 this引用在此“逸出”
}

public static void writer() {
    new FinalReferenceEscapeExample ();
}

public static void reader {
    if (obj != null) {                     //3
        int temp = obj.i;                 //4
    }
}
}

仮定するスレッドAはライター()メソッドを実行し、別のスレッドBは、リーダー()メソッドを実行します。2スレッドBが終了していない前に、操作対象がここで目に見えるように構成されるようになっています。2操作はコンストラクタは、スレッドのオペレータ2及び後列動作は、読み取り実行する場合であっても()メソッドは、まだ最終的なフィールドの値が初期化されて表示されないことがあり、最後のステップは、であってもため、プログラムの動作場合操作2〜並べ替えてもよいです。以下に示すように実際のタイミングを行ってもよいです。

 

図から、我々はそれを見ることができます:コンストラクタが参照されるオブジェクトは、ドメインが初期化されていないため、最終的に構成された他のスレッドには見えない戻る前に。コンストラクタ戻った後、任意のスレッドが正しく初期化され、最終的なフィールドの後に値を見ることが保証されます。

プロセッサに実装セマンティック最終

今、私たちは、最終的なプロセッサを実現するために、例えば、特定のセマンティクスをプロセッサにはx86。

StoreStoreバリア画面を挿入する前に、コンストラクタの戻り、最後のフィールドの後に書面でEDを解釈するために、我々はルールを並べ替え、最終的なフィールドを作成し、上記のように求められます。フィールドを読んで、最終的な並べ替え規則は、コンパイラは、読み出し動作ドメインの前でLoadLoad最終障壁を挿入し必要があります。

x86プロセッサは書き込まれませんので - 、並べ替えは、x86プロセッサでそう書く最後のフィールドに必要なStoreStoreバリアスクリーンは省略する書きます。x86プロセッサの動作がないのでまた、間接的な依存関係は、並べ替え、そうのx86プロセッサ、読み込みバリアが、最終的なフィールドが省略される必要がありますLoadLoadください。これは、x86プロセッサで、ドメイン/書き込みの最後の読み取りは、任意のメモリバリアを挿入しません!

JSR-133は、セマンティック最終的に強化する理由

古いJavaのメモリモデルでは、最も深刻な欠陥は値が最終的なフィールドを変更します表示される場合がありますスレッドです。たとえば、ときスレッドが現在(という(まだ初期化する前にデフォルト値を)形成外科の最後のフィールドは、このフィールドのこのスレッドの最終値を読みに行く時間の期間の後、0の値を見て、その値が1になっていることが分かりますスレッドの初期化後の値)。最も一般的な例は、古いJavaのメモリモデルで、文字列の値が変更される可能性があり(参考文献2には、具体的な例であり、興味のある読者は、ここでは詳細に入る、自分自身にない参照することができます)。

この脆弱性を修正するには、JSR-133専門家グループは、セマンティック最終的に向上させます。最終的に読み書きするためのフィールドを高め、再順序付け規則により、初期化はJavaプログラマのためのセキュリティを提供することができます。オブジェクトが適切に(コンストラクタ構築されたオブジェクトには、「エスケープ」を引用していない)で構築される限り、あなたは、同期を使用する必要はありません(ロックや揮発性の使用を意味する)、あなたは最後のフィールドは、コンストラクタで初期化された後、いずれかのスレッドが値を見ることができるようにすることができます。

リファレンス

  1.  実践でのJava並行処理

  2.  JSR 133(Javaのメモリモデル)よくある質問

  3.  実践でのJava並行処理

  4.  コンパイラ作家のためのJSR-133クック

インテル®64およびIA-32 ArchitecturesvSoftware・デベロッパーズ・マニュアルボリューム3A:システム・プログラミング・ガイド、パート1

 

より多くの川や湖]のマイクロチャネル公衆番号に注意してください[Javaテクノロジ

 

技術的な駅アリのJavaエンジニア。ジャワにコミット学習経験を、共有するためにSSM、SpringBoot、MySQLの、分散、ミドルウェア、クラスタ、Linuxでは、ネットワーク、マルチスレッド、時折話すポイントドッカー、ELKだけでなく、ドライ商品や技術著者:黄斜めには、Javaの関連技術に焦点を当てフルスタックの開発!(国民の関心の後には、返信の「Java」Javaは、基本的な高度、およびプロジェクトの建築家や他の無料の学習教材、より多くのデータベース、分散、サービス及びその他の人気のマイクロ学習ビデオ技術、豊富なコンテンツ、理論と実践の両方を受け取ることはできませんまた、研究ガイドのジャワ、Javaプログラマのインタビューガイドおよびその他の呉服資源の原作者となります提示)

おすすめ

転載: www.cnblogs.com/xll1025/p/11335062.html