Javaのメモリモデル(4)の深い理解 - 揮発性

揮発性の特性

私たちは揮発性であることを共有変数を宣言すると、読み取り/書き込みは、この変数のための非常に特別なことでしょう。揮発性の特性を理解するための良い方法は以下のとおりです。volatile変数/書き込みの単一の読み取り、同期して行われ、単一のリード/ライト動作のためのロックと同じモニターを使用します。次のサンプルコードを参照して、特定の実施例により説明してみましょう:

class VolatileFeaturesExample {
    volatile long vl = 0L;  // 使用 volatile 声明 64 位的 long 型变量 

    public void set(long l) {
        vl = l;   // 单个 volatile 变量的写 
    }

    public void getAndIncrement () {
        vl++;    // 复合(多个)volatile 变量的读 / 写 
    }


    public long get() {
        return vl;   // 单个 volatile 变量的读 
    }
}

このプログラムは、意味的に等価であり、以下の手順で複数のスレッドがプログラム上記の3つのメソッド呼び出しがあるとします。 

class VolatileFeaturesExample {
    long vl = 0L;               // 64 位的 long 型普通变量 

    public synchronized void set(long l) {     // 对单个的普通 变量的写用同一个监视器同步 
        vl = l;
    }

    public void getAndIncrement () { // 普通方法调用 
        long temp = get();           // 调用已同步的读方法 
        temp += 1L;                  // 普通写操作 
        set(temp);                   // 调用已同步的写方法 
    }
    public synchronized long get() { 
    // 对单个的普通变量的读用同一个监视器同步 
        return vl;
    }
}

示されるように、揮発性可変プログラム/書き込み動作上の単一の読み出し例えば、変数の一般的な読み出し/書き込み動作は、同期させるために同じモニターロック、同じ効果その間を使用します。

たまたま-前に、この揮発性変数の見ることが常に可能な手段は、volatile変数の読み取りモニターの二つのスレッド間のメモリモニタの可視性への保証を定期的にモニターロック解除とアクセス、(任意のスレッド)最終書き込み。

モニターロックのセマンティクスは、原子コードのクリティカルセクションの実行を決定します。64ビット長とdouble型の変数は、限り、それは揮発性変数であるように、変数が原子読み込まれるこの手段。複数の動作または原子全体を持っていない同様の揮発性++揮発性化合物、このような操作、もし。

要するに、揮発性変数自体は、次の特性があります。

  • 可視性。この揮発性変数(任意のスレッド)最後の書き込みを見ることができ、常に、揮発性変数の読み込み。
  • アトミック:原子の任意の単一可変揮発性/書き込みの読み取り、そのような複合動作と同様に、原子++揮発性ではありません。

揮発書き込みは - 読書の間の関係を確立する前に発生します

volatile変数独自の特徴である上述したように、プログラマは、揮発性メモリの可視スレッドの影響は、揮発性の独自の特徴よりも重要ですが、また、我々の注意を必要としています。

JSR-133、volatile変数の書き込みを開始 - スレッド間の読み取りの通信が実現できます。

ビューのメモリセマンティックな観点から、揮発性の監視ロックと同じ効果を持っている:揮発性書き込みとモニターのリリースは同じ意味記憶を持って、揮発性メモリを取得することと同じ意味を持っているモニターをお読みください。

volatile変数以下のサンプルコードを参照してください。

class VolatileExample {
    int a = 0;
    volatile boolean flag = false;

    public void writer() {
        a = 1;                   //1
        flag = true;               //2
    }

    public void reader() {
        if (flag) {                //3
            int i =  a;           //4
            ……
        }
    }
}

スレッドAの実行がライター()メソッドと仮定された後、スレッドBは、リーダー()メソッドを実行します。係るルールの前に発生し、関係の確立の前に起こるプロセスは、2つのカテゴリに分けることができます。

  1. プログラムシーケンスルール、1は2の前に起こる、3、4の前に起こります。
  2. 揮発性のルールは、2は3の前に起こります。
  3. ルールは、1 4の前に発生する前に、転送が起こります。

次のような関係の前にパターン化された症状が起こります。

図では、矢印の各リンクの2つのノードが、関係の前に起こる表します。黒矢印番組配列規則、オレンジの矢印は、揮発性のルールを示し、青色の矢印は、組成物が提供することを保証するために、ルールの前にたまたま示しています。

スレッドは、ここで揮発性の変数を書き込んだ後、Bのスレッドが同じ揮発性の変数をお読みください。同じ揮発性の変数を読んだ後、揮発性の変数全ての可視共有変数、スレッドBを書き込む前に、スレッドは、すぐにBをスレッドに見えるようになります

揮発書き込み - 読み意味記憶

揮発性メモリのセマンティクスを書く次のとおりです。

  • 揮発性の変数を書くとき、JMMはローカルメモリの共有変数を対応するスレッドは、メインメモリにフラッシュされます。

上記の例のプログラムVolatileExample例では、次いで、Bリーダー()メソッドを実行したスレッドは、その最初の実行にスレッドAライタ()メソッドを想定し、最初の2つのスレッドローカルメモリとフラグは初期状態です。図は、スレッドA揮発性書き込み後、共有変数概略の状態を実行します。

スレッドAフラグ変数を書き込んだ後、上記のように、ローカルメモリAスレッドの値が更新された2つのA共有変数は、メインメモリにフラッシュされます。このとき、ローカルメモリの値とメインメモリA共有変数が一致しています。

次のように読み込むための不揮発性メモリの意味:

  • 揮発性の変数を読み取るとき、JMMはローカルメモリに対応するスレッドがディアサートされます。そして、メインスレッドがメモリから共有変数を読み込みます。

以下に、スレッドBが、同じ揮発性変数、共有変数の状態を模式的に読み取ります。

上記のように、フラグ変数を読んだ後、ローカルメモリBがデアサートされています。このとき、スレッドBは、メインメモリ共有変数から読み取る必要があります。操作スレッドBを読むと値Bとメインメモリ内のローカルメモリの共有変数も同じになる原因となります。

、スレッドBを読んだ後、この揮発性変数を書き込む前に、書き込みスレッドAをvolatile変数を読み込む場合は、私たち揮発書き込みや揮発性の読み取り一緒にこの2つのステップを参照してください場合は共有変数のすべての可視値がすぐにスレッドを読むためになりますB見えます。

次に、揮発性および揮発性読み書きメモリセマンティクス要約されるように:

  • スレッド書き込み揮発性変数は、本質的に次のメッセージに(その共有変数の変更)は、この揮発性変数をスレッドに読み取られる発行されたスレッドAです。
  • スレッドBはスレッドBは、前にスレッドによって送信されたメッセージ(共有変数への変更、この揮発性変数を書き込む前に)受信し、本質的に、揮発性変数を読み出します。
  • スレッドは、プロセスが本質的にスレッドAがメインメモリを介して、スレッドBにメッセージを送信で、書き込みに揮発性変数は、その後、Bは、揮発性の変数を読み取るスレッドです。

揮発性メモリの実装セマンティック

さて、JMMは、揮発性の書き込み/読み取りを達成するためにどのよう意味記憶で見てみましょう。

我々は以前に並べ替えにコンパイルされ、落胆プロセッサの並べ替えを並べ替え述べました。揮発性メモリセマンティクスを達成するために、JMMは、それぞれ、両方のタイプの並べ替えの種類を制限します。以下は、JMM揮発性の並べ替えルールテーブル用に開発されたコンパイラです。

缶並べ替え 第2の動作
最初の操作 通常の読み出し/書き込み 揮発読みます 揮発書き込み
通常の読み出し/書き込み     番号
揮発読みます 番号 番号 番号
揮発書き込み   番号 番号

例えば、最後のセル手段の3行目:プログラム順序で、最初の動作は、コンパイラができないリオーダこの揮発性あたかも第2の書き込み動作、読み出しまたは書き込みの単純な変数であります二つの操作。

表から、我々は見ることができます:

  • 第2の操作は、書き込みの揮発性の時間になると、何があっは、並べ替えることができない最初の操作です。このルール揮発書き込み動作のことを保証しますがコンパイルされることはありません前には、書き込みに注文した後、揮発性の高いと考えています。
  • 最初の操作は、揮発性の読み取りである場合には、第2の操作が何であるかに関係なく、それを並べ替えることができないとき、。順序が揮発する前に読むために落胆した後、揮発性の読み出し動作がコンパイルされないことをこの規則は保証します。
  • 最初の書き込み動作がある場合に揮発性、揮発性の第2の動作は、読み出し、無並べ替えです。

揮発性メモリセマンティクスを達成するために、生成バイトコードは、プロセッサ並べ替えの特定の型を阻害する命令シーケンスメモリバリアに挿入されるコンパイラ。コンパイラのために、障害を最小限にするために、挿入の総数の最適な配置を見出すことは、したがって、JMMは、保守的な戦略を取る、ほとんど不可能です。以下は、保守的な戦略JMMメモリバリア挿入戦略に基づいています。

  • 各先行する書き込み動作は、揮発性StoreStoreバリアを挿入します。
  • ライトバック動作は、揮発性StoreLoadバリアのそれぞれに挿入されています。
  • 各読み出し動作後の揮発性LoadLoadバリアを挿入します。
  • 各読み出し動作後の揮発性LoadStoreバリアを挿入します。

戦略上の挿入メモリバリアは、非常に保守的であるが、それは任意のプロセッサプラットフォームは、任意のプログラムが正しい揮発性メモリセマンティクスを得ることができるようにすることができます。

以下は、保守的な戦略、メモリバリアを挿入した後に発生する揮発概略書き込みコマンドシーケンスです。

すべての通常の書き込み動作の揮発書き込みフロントはプロセッサのいずれかで見られることがある前にStoreStoreバリア上の図は、保証することができます。StoreStore障壁が揮発書き込み前にメインメモリにフラッシュされ、上記一般書き込みのすべてを保護するためです。

もっとここで興味深いことは揮発性書き込みStoreLoadバリアの背後にあります。バリアの役割は、揮発書き込みバックを避けるために、揮発性の読み取り/書き込みの並べ替えを持っていることです。コンパイラは、多くの場合、正確に、後に揮発性で書い判断できないので、必要性は(例えば、復帰後すぐに揮発性の書き込み方法)StoreLoadバリアを挿入します。各揮発性書き込みバックで各揮発性の読み取りの前でStoreLoad障壁を挿入します。意味的に正しい揮発性メモリを実現するために確保するために、JMMは、ここに保守的な戦略を取りました。全体的な効率の考慮の観点から、JMMは、揮発書き込みStoreLoadバリアに挿入各背後選択しました。揮発書き込みなので - 一般的な使用パターンを読み取り、メモリセマンティクスです:volatile変数、同じ揮発性の変数を読み込み、複数のリーダースレッドを書き込むためのライター・スレッド。スレッドの数がはるかに書き込み超えるスレッドを読むことをすると、かなりの効率改善をもたらす揮発書き込み後に挿入StoreLoad障壁を選択します。ここからは、JMMに実装された特徴を見ることができます:まず正確さを確保し、効率の追求に行きます。

以下は、保守的な戦略、挿入シーケンス図の後に生成された揮発性読み出し命令メモリバリアです。

上記と通常揮発性リード並べ替えの下を読み取るためにプロセッサを無効にするLoadLoadバリア上記図。上記および下記通常揮発書き込み並べ替えを読み取るためにプロセッサを無効にするLoadStoreバリア。

上記揮発性および揮発性読み書きメモリバリア挿入戦略は非常に保守的です。実際の実装では、揮発書き込み変えることなく、 - メモリ読み出しセマンティクスを、コンパイラ不要なバリアは、状況に応じて省略してもよいです。以下は、私たちがして、コードの具体的な例を説明します。

class VolatileBarrierExample {
    int a;
    volatile int v1 = 1;
    volatile int v2 = 2;

    void readAndWrite() {
        int i = v1;           // 第一个 volatile 读 
        int j = v2;           // 第二个 volatile 读 
        a = i + j;            // 普通写 
        v1 = i + 1;          // 第一个 volatile 写 
        v2 = j * 2;          // 第二个 volatile 写 
    }

    …                    // 其他方法 
}

readAndWrite()メソッドのために、コンパイラは、次のように最適化時にバイトコードを行うことができる生成されます。

StoreLoad障壁の最後を省略することができないことに注意してください。第二揮発性の書き込み方法は、すぐに復帰した後ので。このとき、コンパイラは正確に揮発性の後に読み取りまたは書き込みがあるかどうかを判断しないことがあり、安全上の理由のために、コンパイラは、多くの場合、ここでStoreLoad障壁を挿入します。

異なるプロセッサによる任意のプロセッサプラットフォームは、プロセッサ・メモリ・モデルにおける異なる「気密性」を有するため、上記の最適化は、メモリバリアインサートは、特定のモデルに応じて最適化プロセッサメモリに継続することができました。x86プロセッサでは、例えば、最後の障壁を除く上記画像は、他のバリアは省略するStoreLoad。

x86プロセッサ・プラットフォームにおける保守的な戦略の前に揮発性読み出し及び書き込みに最適化することができます。

前に述べたように、x86プロセッサのみを書きます - 読み取りが並べ替えます。、読み取りを読んで - - X86は読まない書き込みや書き込みを - 書き込み操作は、並べ替えんので、x86プロセッサは、メモリバリアの種類に対応するこれらの3つの操作を省略します。x86のでは、JMM StoreLoadバリアの挿入のみ揮発性のライトバックが正しい揮発書き込みを実現することができます - メモリ読み出しセマンティクスが。(実行StoreLoadバリアオーバーヘッドが比較的大きくなるため)x86プロセッサ、コストよりも揮発性の揮発性リードライトオーバーヘッドがはるかに大きくなることをこれは意味。

JSR-133は、揮発性メモリのセマンティクスを強化する理由

古いJSR-133のJavaメモリモデルでは、前に揮発性の変数間の並べ替え許されていないが、しかし、古いJavaのメモリモデルは、揮発性と通常の変数の間で並べ替えることができます。古いメモリモデルでは、VolatileExampleの典型的なプログラムは次の順序に並べ替えられることが実行されます。

旧メモリモデルでは、1及び2ないデータ依存の間、1と2の間(図3、図4と同様に)並べ替えすることができました。結果は:読み取りスレッドBの実行4、共有変数午前1時の実装で書き込みスレッドAの変形を見ることができないかもしれません。

だから、古いメモリモデルで、揮発書き込み - モニターを解放しませんでした読んで - ウォンはメモリーセマンティクスを持っています。モニタロックスレッドより軽量の間の通信のためのメカニズムを提供するために、JSR-133拡張グループは、揮発性メモリセマンティクスを決定した:厳密通常リオーダリング変数をvolatile変数に限定コンパイラプロセッサは確実にするために揮発書き込み - 読み取りおよびモニタリリース - 同じを取得し、同じメモリセマンティクスを持っています。揮発性メモリセマンティクスを破壊することができる揮発性変数と共通の変数間の並べ替え限り、観点からコンパイル高い点と照合プロセッサメモリバリア挿入戦略は、この並べ替えは落胆照合プロセッサとメモリバリアをコンパイルします挿入ポリシーは禁止します。

揮発性読み取り専用個々揮発性変数が原子/プロパティを書き込むことを保証し、コードの全体原子クリティカルセクションの実行を確実にするために実行されるモニタ相互排他ロックの特性は。機能的には、モニターロックは揮発性よりも強力で、拡張性と実行のパフォーマンス、揮発性の利点に。読者は揮発性プログラムロックでモニターを交換したい場合は、慎重にしてください。

 

公開された136元の記事 ウォンの賞賛6 ビュー1487

おすすめ

転載: blog.csdn.net/weixin_42073629/article/details/104741609