序文
前の記事では、Atomic*クラスに言及したCASの原則について説明しました。原子動作を実装するメカニズムは、揮発性のメモリ可視性特性に依存しています。 CasとAtomic*がまだわからない場合は、Cas Spin Lockが話していることを確認することをお勧めします。
並行性の3つの特性
まず、揮発性を使用したい場合は、マルチスレッドの並行性環境にある必要があります。私たちがよく話す同時シナリオには、原子性、可視性、秩序性の3つの重要な特性があります。これら3つの特性が満たされた場合にのみ、同時プログラムを正しく実行できます。そうしないと、さまざまな問題が発生します。
Atomicity、前の記事で説明したCASおよびAtomic*クラスは、単純な操作の原子性を確保できます。一部の責任操作では、同期またはさまざまなロックを使用して実装できます。
可視性とは、複数のスレッドが同じ変数にアクセスし、1つのスレッドが変数の値を変更し、他のスレッドがすぐに変更された値を確認できることを指します。
注文すると、プログラムの実行の順序はコードの順序で実行され、指示は再注文されることは禁止されています。これが当てはまらないのは当然のようです。命令の並べ替えは、命令を最適化し、プログラムの操作効率を改善し、シングルスレッドプログラムの実行結果に影響を与えることなく並列性を改善するためのJVMです。ただし、マルチスレッド環境では、一部のコードの順序が論理的な不正確さを引き起こす可能性があります。
揮発性は、可視性と順序の2つの機能を実装します。したがって、マルチスレッド環境では、これら2つの機能の機能を確保する必要があり、揮発性キーワードを使用できます。
揮発性が可視性をどのように保証するか
可視性に関しては、コンピューターのプロセッサとメインメモリを理解する必要があります。マルチスレッドのため、スレッドがいくつあるとしても、最終的にはコンピュータープロセッサで実行されます。今日のコンピューターは基本的にマルチコアであり、一部のマシンにはマルチプロセッサもあります。マルチプロセッサの構造図を見てみましょう。
これは、2つのプロセッサ、クアッドコアを備えたCPUです。プロセッサは物理スロットに対応し、複数のプロセッサがQPIバスを介して接続されます。プロセッサは、複数のコアと、プロセッサ間でマルチコア共有L3キャッシュで構成されています。コアには、レジスタ、L1キャッシュ、L2キャッシュが含まれます。
プログラムの実行中に、データの読み取りと執筆が関与する必要があります。メモリアクセス速度はすでに非常に高速ですが、CPUの実行命令の速度よりもはるかに劣っていることは誰もが知っています。したがって、カーネルでは、L1、L2、およびL3レベル3のキャッシュが追加されます。このようにして、プログラムが実行されているとき、必要なデータが最初にメインメモリからコアのキャッシュにコピーされ、操作が完了した後、メインメモリに書き込まれます。次の図は、レジスタからキャッシュ、メインメモリ、さらにはハードディスクまで、データにアクセスするCPUの概略図であるため、速度が遅くなります。
CPU構造を理解した後、プログラムの実行の特定のプロセスを見て、単純な自己障害操作を例にとってみましょう。
i = i+1;
このステートメントを実行すると、コアで実行されているスレッドは、Iの値をコアが配置されているキャッシュにコピーします。操作が完了すると、メインメモリに書き戻されます。マルチスレッド環境では、各スレッドには、実行中のコアのキャッシュ領域に対応するワーキングメモリがあります。つまり、各スレッドには、操作に必要なレプリカデータを保存するための独自のプライベートワーキングキャッシュ領域があります。次に、I+1の問題を見てみましょう。 Iの初期値が0であると仮定すると、このステートメントを同時に実行する2つのスレッドがあり、各スレッドが実行するには3つのステップが必要です。
1.メインメモリからスレッドワーキングメモリ、つまり対応するカーネルキャッシュ領域までのI値を読み取ります。
2。i+1の値を計算します。
3.結果値をメインメモリに戻します。
2つのスレッドがそれぞれ10,000回実行された後、期待値は20,000にする必要があります。残念ながら、Iの値は常に20,000未満です。この問題の理由の1つは、キャッシュの一貫性の問題です。この例では、スレッドのキャッシュコピーが変更されると、他のスレッドのキャッシュコピーをすぐに無効にする必要があります。
揮発性キーワードを使用した後、次の効果は次のとおりです。
1.変数が変更されるたびに、プロセッサキャッシュ(ワーキングメモリ)がメインメモリに書き戻されます。
2。ワーキングメモリのメインメモリに戻すと、他のスレッドのプロセッサキャッシュ(ワーキングメモリ)が無効になります。
揮発性はメモリの可視性を保証するため、実際にはCPUによるキャッシュの一貫性を保証するMesiプロトコルを使用します。 Mesiプロトコルには多くの内容があるので、ここでは説明しません。自分でチェックしてください。要するに、揮発性キーワードが使用されます。揮発性変数へのスレッドの変更がすぐにメインメモリに書き戻され、他のスレッドのキャッシュラインが無効になり、他のスレッドが変数を再度使用することを余儀なくされると、メインメモリから読み取る必要があります。
次に、上記の変数を揮発性で変更し、再度実行します。各スレッドは10,000回実行します。残念ながら、まだ20,000未満です。なぜこれがなぜですか?
Volatileは、CPUのMESIプロトコルを利用して視界を確保します。ただし、この自己増加操作は3つのステップに分割されるため、揮発性は操作の原子性を保証しないことに注意してください。スレッド1がメインメモリからI値を読み取ると仮定して、それが10であると仮定し、この時点で詰まりが発生しますが、私はまだ変更されていません。この時点で、スレッド2はメインメモリからi値も読み取ります。この時点で、これらの2つのスレッドで読み取られるi値は同じです。この時点で、MESIプロトコルによると、スレッド1の作業メモリに対応するキャッシュラインは、はい、無効な状態に設定されます。ただし、スレッド1はすでにメインメモリからi値をコピーしているため、1を追加してメインメモリに書き戻す操作が必要であることに注意してください。どちらのスレッドも10に基づいて1を追加してからメインメモリに書き戻すため、メインメモリの最終的な値は予想される12ではなく11です。
したがって、揮発性を使用すると、メモリの可視性が確保されますが、原子性を保証することはできません。原子性がまだ必要な場合は、この前の記事を参照できます。
どのように揮発性が秩序を保証するか
Javaメモリモデルには、いくつかの生来の「Orderline」があります。つまり、手段なしで保証できます。これは通常、発生前の原則と呼ばれます。 2つの操作の実行命令を実現前の原則から導き出すことができない場合、秩序性を保証することはできず、仮想マシンはそれらを自由に並べ替えることができます。
以下は、「Java Virtual Machinesの詳細な理解」から抜粋した、以前に発生する8つの原則です。
ここでは、主に揮発性キーワードのルールについて説明し、有名なシングルトンパターンで二重チェックの例を挙げます。
クラスSingleton {private volatile static singleton instance = null; private singleton(){} public static singleton getInstance(){if(instance == null){// synchronized(singleton.class){if(instance == null)//ステップ2 instance = new singleton(); //ステップ3}}インスタンスを返します。 }}インスタンスが揮発性で変更されていない場合、どのような結果が生成されますか? getInstance()メソッドを呼び出す2つのスレッドがあるとします。スレッド1はStep1を実行し、インスタンスがnullであることを発見し、シングルトンクラスを同期してロックします。次に、インスタンスが再びnullであるかどうかを判断し、それがまだnullであることを発見し、ステップ3を実行してシングルトンのインスタンス化を開始します。インスタンス化プロセス中、スレッド2はステップ1に進み、インスタンスが空ではないことがわかりますが、現時点ではインスタンスが完全に初期化されない場合があります。
それはどういう意味ですか?オブジェクトは3つのステップで初期化され、次の擬似コードで表されます。
メモリ= allocate(); // 1。オブジェクトctorinstance(メモリ)のメモリ空間を割り当てます。 // 2。 Object Instance = Memoryを初期化します。 // 3。オブジェクトを指すオブジェクトのメモリスペースを設定します
ステップ2とステップ3はステップ1に依存する必要があり、ステップ2とステップ3には依存関係がないため、これらの2つのステートメントが命令の再配置、つまりステップ3がステップ2の前に実行される可能性があります。この場合、ステップ3はまだ実行されていませんが、それはまだ実行されていません。ちょうど今、スレッド2はインスタンスがnullではないと判断しているため、インスタンスインスタンスを直接返します。ただし、現時点では、インスタンスは実際には不完全なオブジェクトであるため、使用すると問題が発生します。
揮発性キーワードを使用すると、「揮発性によって変更された変数を書き込むこと、次の時間に変数を読み取る前に」という原理を使用することは、上記の初期化プロセスに対応します。ステップ2と3はどちらも執筆インスタンスであるため、インスタンスを読み取るときに後で発生する必要があります。つまり、完全に初期化されていないインスタンスを返す可能性はありません。
基礎となるJVMは、「メモリバリア」と呼ばれるものを通じて行われます。メモリフェンスとも呼ばれるメモリバリアは、メモリ操作に連続的な制限を実装するために使用されるプロセッサ命令のセットです。
やっと
揮発性のキーワードを通して、同時プログラミングの可視性と順序性について学びました。これはもちろん、単なる理解です。より深い理解のために、あなたはそれを自分で勉強するためにあなたのクラスメートに頼らなければなりません。
関連記事
私たちが話しているCASスピンロックは何ですか
要約します
上記は、この記事のコンテンツ全体です。この記事の内容には、すべての人の研究や仕事に特定の参照値があることを願っています。ご質問がある場合は、メッセージを残してコミュニケーションをとることができます。 wulin.comへのご支援ありがとうございます。