あなたは本当に揮発性のキーワードを理解していますか?

今日、私たちは、Javaでの並行プログラミングの知識を探求するために協力しましょう:volatileキーワード

この記事では以下の3点から揮発性のキーワードについて説明します。

  1. どのようなキーワードは、揮発性ですか?
  2. volatileキーワードは、すべての問題を解決することができますか?どのような利用シーン?
  3. 原則volatileキーワードの実現?

どのようなキーワードは、揮発性ですか?

公式文書では、SunのJDKは、揮発性を記述することです。

The Java programming language provides a secondmechanism, volatile fields, that is more convenient than locking for somepurposes. A field may be declared volatile, in which case the Java Memory Modelensures that all threads see a consistent value for the variable.

つまり、変数がvolatileキーワードに追加された場合は、コンパイラとJVMのメモリモデルに指示されます。この変数はすべてのスレッドで共有され、目に見える、各JVMは、書かれた最新の値を読み取り、それを作りますすべての可視の最新CPU値。揮発性は、スレッドの可視性を保証し、一定の秩序を提供していますが、原子性を保証することはできません。根底にあるJVMで揮発性メモリバリアを達成するために使用されます。

これにより、我々は2つの揮発性の特性があることを知ることができます。

  1. 視認性を確保、原子性を保証するものではありません。
  2. 並べ替え命令を禁止

原子と可視性

アトミックは、1つまたは複数の操作を意味するか、すべての操作を行い、プロセスが任意の因子の実行を中断しない、または実装されないであろう。成功するか失敗のどちらかアクションのセットとしての性質とトランザクションデータベース。いくつかの簡単な原子を理解するために、次の例を参照してください。

i == 0;       //1
j = i;        //2
i++;          //3
i = j + 1;    //4

答えを見る前に、あなたはアトミック操作である、上記の4つの操作を考えることができますか?非アトミックな操作とは何ですか?

答えは明らかにされています。

1——是:在Java中,对基本数据类型的变量赋值操作都是原子性操作(Java 有八大基本数据类型,分别是byte,short,int,long,char,float,double,boolean)
2——不是:包含两个动作:读取 i 值,将 i 值赋值给 j
3——不是:包含了三个动作:读取 i 值,i+1,将 i+1 结果赋值给 i
4——不是:包含了三个动作:读取 j 值,j+1,将 j+1 结果赋值给 i

すなわち、単純な読み出し割り当てである(デジタル及び変数に割り当てる必要があり、各割り当ては、変数間のアトミック操作ではない)アトミック操作です。

注:以前のオペレーティングシステムは、32ビット、64ビットのデータであるために(long型、ダブル型)8バイトで、64ビットの合計が広い、変数を使用して、2つの操作に割り当てを完了するために必要であるか、またはJavaで読み取ります取得操作。64ビットのオペレーティングシステムが普及すると、HotSpotのJVMが64ビットで実装され、64ビットのデータ(ロングタイプ、ダブルタイプ)の原子処理を行う(原因JVM仕様に指定されていない、またはJVMの実装は他を排除するものではありません32ビットモードに処理されました)。

シングルスレッド環境では、我々はこれらのステップはアトミック操作であると考えていますが、マルチスレッド環境では、Javaは唯一、上記の基本データ型の割り当てがアトミックであることを保証することができ、他の操作ミスは操作の過程で発生する可能性があります。この目的のために、マルチスレッド環境なロックとsynchronizedキーワードの導入など、いくつかの操作のアトミック性を確保するためです。

変数の可視性を確保するために、volatileキーワードの上に言えば、それは原子性を保証するものではありません。アトミックが言われてきた、以下の下の視認性は述べています。

実際には、視認性とJavaのメモリモデルについての設定:Javaメモリー・モデルは、各スレッドは、独自のワーキングメモリ(プライベートメモリ)は、すべての変数は、メインメモリ(スレッド共有領域)の間に存在していることを指定します。変数のすべての操作のスレッドが動作するために、メインメモリに直接ワーキングメモリではなく、でなければなりません。そして、各スレッドは、他のスレッドの作業メモリにアクセスすることはできません。

ここでは簡単な栗は以下のとおりです。

例えば、私はJavaで、++上記の動作は、実行i++文を:

まず、iは作業メモリにメインメモリ(生値)から読み出され、その後、ワーキングメモリ(私のメインメモリ不変値)で演算+1を行い、演算結果が、最終的にメインメモリにフラッシュで実行スレッド。

データ操作は、操作を実行した後、スレッド、スレッドを実行するプライベートメモリで行われる、動作は、必ずしもメインメモリに直ちにフラッシュもたらさないであろう(最後にメインメモリを更新するが)、動作はCPUによってメインメモリにフラッシュされます適切なタイム・トリガを選択してください。他のスレッドが読んで(むしろメインメモリワーキングメモリよりも、データの優先順位を読む)したときに値が、メインメモリに更新されていない、この時間はまだ古いメインメモリの元の値とすることができると仮定する前に、それはにつながる可能性があり操作エラーの結果。

次のコードは、テストコードです:

package com.wupx.test;

/**
 * @author wupx
 * @date 2019/10/31
 */
public class VolatileTest {

    private boolean flag = false;

    class ThreadOne implements Runnable {
        @Override
        public void run() {
            while (!flag) {
                System.out.println("执行操作");
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("任务停止");
        }
    }

    class ThreadTwo implements Runnable {
        @Override
        public void run() {
            try {
                Thread.sleep(2000L);
                System.out.println("flag 状态改变");
                flag = true;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        VolatileTest testVolatile = new VolatileTest();
        Thread thread1 = new Thread(testVolatile.new ThreadOne());
        Thread thread2 = new Thread(testVolatile.new ThreadTwo());
        thread1.start();
        thread2.start();
    }
}

スレッド2の完全な実行フラグ=真後の可能性が高いこれらの結果は、スレッド1がメインメモリタイミングにフラグ状態があるので、すぐにプライベートメモリ、スレッド2で上記のすべて、サイクルを停止することができる、とフラッシュがない間ことを保証するものではありません。固定され、スレッド1、読み取りフラグ値は、まだ独自のプライベートメモリ、メモリフラグとプライベートスレッド1に偽であるので、それは、スレッドがループしながら、継続する可能性があります。結果は以下の通りであります:

执行操作
执行操作
执行操作
flag 状态改变
任务停止

これらの予測できない問題が揮発性のキーワードはフラグが変更されて、共有変数の揮発性の変更は、他のスレッドがプライベート・メモリからではなく、変数を操作する必要がある場合、内部のメインメモリに操作した直後に更新され、その値を保証するために変更することができますが発生しないでください読みが、新しい値を強制するには、メインメモリから読み出されます。あるスレッドが変数の値を変更すること、他のスレッドにこの新しい値が直ちに表示されています。

命令の並べ替え

一般的に、プロセスの効率を向上させるためにプロセッサは、コードの最適化を入力するかもしれないが、それはコードの順序と一致ために、個々のステートメントでのプログラムの実施を保証するものではありませんが、それはプログラムの実装とコードの実行順序の最終結果を保証します結果は同じです。

例えば、次のコード

int i = 0;             
boolean flag = false;
i = 1;        // 1
flag = true;  // 2

INTコードは、変数が2つの代入され、変数を定義するブール変数を定義します。観点から、コードシーケンス、文は2の前の文は、その後、あなたが実際にこのコードを実行するJVMは、文は2が行う前文で実行されることを保証しますでしょうか?必ずしもそうではありません、なぜですか?そこは、並べ替えするように指示することができる(InstructionReorder)が発生します。

プログラムに影響を与えなかった最終的な結果を実行する必要があり、文1と文2は、それが実装プロセスで可能である、文の2文は最初の実行1後に実行されます。

プロセッサは、命令の順序を変更するが、しかし、注意してください、それは、プログラムが最終結果と同じ結果を実行するコードの順序であろうことを保証する、それを確実にするために、それに依存していますか?次の例を見て:

int a = 10;     // 1
int r = 2;      // 2
a = a + 3;      // 3
r = a * a;      // 4

プロセッサは、再を行うため、このコード配列は、変更されない1-> 2-> 3-> 4または2-> 1->> 4 3-、3および4を実行するが、順番に行われてもよいです命令間のデータ依存性を検討するソートするとき命令命令2命令1の結果は、プロセッサは、命令1命令2を確実にする前に実行される場合、使用されなければなりません。

一方で並べ替えが実行の単一スレッドの結果には影響しませんが、それをマルチスレッド?私たちは、以下の例を参照してください。

// 线程1
String config = initConfig();    // 1
boolean inited = true;           // 2
 
// 线程2
while(!inited){
       sleep();
}
doSomeThingWithConfig(config);

上記のコードでは、ステートメントの文1及び2はそこにはデータ依存ではないので、並べ替えてもよいです。並べ替えが発生した場合、最初のスレッド1の実行中の文2を実行するが、この時間は、スレッド2は、初期化が完了していると思っているだろう、それはdoSomeThingWithConfig(設定)メソッドを実行するために、whileループの外にジャンプしますが、この時間に、configませんでした初期化され、それはエラーが発生します。

上記からわかるように、命令は、単一スレッドの実行の並べ替えには影響を与えませんが、同時にスレッドを実行の有効性に影響を与えます。

その後、揮発性のキーワードは、変数を変更並べ替え手段を禁止します:

  1. プログラムは、読み出し動作または書き込み動作の揮発性の変数を実行すると、その動作は、上記のすべてを持っている必要がありますし、背面に操作が動作して、その背後に見える確かにすることではありません
  2. 最適化中の命令の後、揮発性変数ステートメント変数揮発性の読み取りの前に置かれ、書き込み操作、もその揮発性変数文に後者はフロントで実行することができますすることができません

栗の場合:

x=0;             // 1
y=1;             // 2
volatile z = 2;  // 3
x=4;             // 4
y=5;             // 5

volatile変数zは変数、命令の並べ替えである場合には、ステートメントはバック文3に文の前に3文の1、2を入れて、また意志文の4、5文はありません。しかし、声明文1および2、4と5の間で一連の文は、すべてのステートメントがステートメント、1と2が完全に終了しなければならない声明文、および文を実行するために、3をvolatileキーワードを保証し、保証することはありません1 2文文の結果は3であり、4文、文5が見えます。

戻る前の例:

// 线程1
String config = initConfig();   // 1
volatile boolean inited = true; // 2
 
// 线程2
while(!inited){
       sleep();
}
 
doSomeThingWithConfig(config);

可能文2に述べた例では、()メソッドは、ときにエラーとなり、それが実行doSomThingWithConfigにつながる可能性があり、ステートメント1の前に実行される前に、彼は言いました。

ここでは揮発性のキーワード変数あれば変更することがinited、あなたは設定が初期化されていることを確認することができるようになります、文が午前2時00分に実行されることを保証することができます。

揮発性のシナリオ

synchronizedキーワードは、コードの一部を実行するために、複数のスレッドを防ぐためです、それは非常に多くの影響力は、プログラム実行の効率化、およびいくつかのケースではvolatileキーワード同期よりも優れたパフォーマンスをしますが、揮発性のキーワードはsynchronizedキーワードに代わるものではありませんので注意してください揮発性のキーワードはアトミック操作を保証することはできませんので。一般的には、揮発性の使用は、次の3つの条件を満たしている必要があります。

  1. 変数の現在の値に依存しない、または単一のスレッドのみが変数の値を更新することを保証するために、変数への書き込み
  2. この変数は、他の状態変数と一緒に不変条件を含まれません
  3. 変数にアクセスするときには、ロックする必要はありません。

上記3つの条件がvolatileキーワードプログラムの使用が高い同時実行で正常に実行できることを確実にするためには、アトミック操作であることが唯一の保証が必要です。唯一のシナリオは揮発性に適している設定または取得、getAndOperate揮発性の状況での使用は推奨されません。

二つの一般的なシナリオは以下のとおりです。

  1. 標識の量を状態
volatile boolean flag = false;

while (!flag) {
    doSomething();
}

public void setFlag () {
    flag = true;
}

volatile boolean inited = false;
// 线程 1
context = loadContext();
inited = true;

// 线程 2
while (!inited) {
    sleep();
}
doSomethingwithconfig(context);
  1. DCLダブルチェックロック - シングルトン
public class Singleton {
    private volatile static Singleton instance = null;

    private Singleton() {
    }

    /**
     * 当第一次调用getInstance()方法时,instance为空,同步操作,保证多线程实例唯一
     * 当第一次后调用getInstance()方法时,instance不为空,不进入同步代码块,减少了不必要的同步
     */
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

推奨される読書:デザインパターン-シングルトン

理由は上記で説明すると、揮発性の並べ替えの使用はすでにについて話しました。主にインスタンスアトミック操作ではありません=新しいシングルトン()、JVMの言葉は、3つのことを実行します。

  1. たとえば、メモリを割り当てます
  2. メンバ変数を初期化するためにシングルトンのコンストラクタの呼び出し
  3. メモリ・ストレージ・スペースを割り当てるインスタンスオブジェクトポイント(null以外に、このステップのインスタンスを実行します)

しかし、JVMタイムコンパイラを並べ替える最適化された命令があり、それは、第2および第3のステップの順序は保証されていない上に、最終的な実行順序は1-2-3であってもよく、それはかもしれである1~3 - 2。後者の場合、2を3を実行する前に、スレッド1の後、スレッドによってプリエンプト2は、非ヌル時間インスタンスを有する(ただし、初期化されていない)、スレッド2は、NULLポインタ例外が返されたインスタンスを使用して報告されます。

揮発性の特性は、それを達成する方法ですか?

フロントは、揮発性のキーワードにいくつかの使用をカバーし、視認性を確保し、命令の並べ替えを禁止する方法を最終的には、揮発性の探求しましょう。

で、「Java仮想マシンの深い理解、」本は言います:

観察を添加し、見つから追加volatileキーワードvolatileキーワード生成されたアセンブリコードは、ロックが、よりプレフィックス命令になり、揮発性のキーワードを追加されません。

次の栗の場合:

揮発性の整数の増分は、(i ++が)、実際には、3つのステップに分けられます:

  1. ローカルに揮発性の読み取り変数の値
  2. 変数の値を増やし
  3. 地元の値はライトバックと他のスレッド表示をしましょう

このJVM命令3つの段階:

mov    0xc(%r10),%r8d ; Load
inc    %r8d           ; Increment
mov    %r8d,0xc(%r10) ; Store
lock addl $0x0,(%rsp) ; StoreLoad Barrier

ロックプリフィックス命令効果的にメモリバリア(とも呼ばれるメモリフェンス)、メモリバリアは、3つの機能を提供します:

  1. これは、次の命令は、メモリバリア命令並べ替え前の位置に排出されないであろう、また後で命令メモリバリアの前に排出されることを保証する、すなわち、その前に、メモリバリアコマンドフレーズを実行するとき操作は、(並べ替えを禁止している)が完了されました
  2. それはすぐにキャッシュメインメモリに書き込まれた操作を変更することを余儀なくされます(満たす可視性)
  3. それが書き込み動作であれば、それは(会うの可視性)に対応した他のCPUのキャッシュラインを無効にします

揮発性可変ルール事前発生される(最初の発生の原理)の可変事前にこの変数の顔リードアフターライト動作が発生しました。(この機能はうまくvolatileキーワードの同時使用の安全性を確保するように変更されているのはなぜDCLはダブルロックシングルトンを確認する説明できます)

概要

変数の型はvolatileとして宣言すると、コンパイラとランタイムは、この変数に気づくでしょう共有されている、それは一緒に並べ替え変数や他のメモリ操作に動作しません。揮発性の変数がvolatile変数を読み込む際に、最も最近書かれた値を返すので、常に、レジスタや目に見えない他の場所にキャッシュされません。

揮発性の変数にアクセスするとき、それは実行スレッドがブロックされていることはありませんので、揮発性変数はsychronized軽量な同期メカニズムよりもキーワードでロック操作を行いません。

メカニズムをロックすると、可視性とアトミック性の両方を確保することができ、揮発性変数は、視認性を確保することができます。

おすすめ

転載: www.cnblogs.com/wupeixuan/p/11769011.html