#volatile キーワードの深い理解
1.volatile と可視性
volatile が可視性を保証できることは誰もが知っていますが、それはどのようにして保証されるのでしょうか? これは、Happen-before 原則に関連しています。この原則の 3 番目の規定は、volatile で変更された変数の場合、書き込み操作は変数の読み取り操作よりも前に行う必要があるということです。具体的な手順は次のとおりです。 A スレッドは共有変数を作業メモリに読み込み、同時に B スレッドも共有変数を作業メモリに読み込みます。 スレッド A が共有変数を変更すると、すぐにメイン メモリに更新されます。このとき、スレッド B の作業メモリ内の共有変数は無効に設定され、新しい値が必要になります。メインメモリから再読み取りされます。ハードウェアに反映され、CPU のキャッシュ ラインが無効な状態に設定されます。 これにより可視性が確保されます。簡単に言うと、スレッドが volatile-modified 変数を変更してメイン メモリに更新した後、他のスレッドの作業メモリにある共有変数が無効になるため、共有変数をスレッドから取得する必要があります。メインメモリ。もう一度読んでください。2. 不安定性と秩序性
不安定性が秩序性を保証できることは誰もが知っていますが、それはどのようにして保証されるのでしょうか? volatile は順序性を保証し、比較的簡単です。これにより、JVM とプロセッサが volatile キーワードで変更された変数の命令を並べ替えることができなくなりますが、変数の前後の変数は、最終結果に限り任意に並べ替えることができます。は変数の値と同じです。変更前の結果の一貫性を保つだけです。基本的な原則
volatile によって変更された変数には、下部に「lock:」というプレフィックスが付けられます。「lock」プレフィックスの付いた命令は、次の命令と同等です。メモリ バリア。これはまさに可視性と秩序性を確保するための鍵です。このバリアの主な機能は次のとおりです: 命令が再配置されるとき、バリアの前のコードはバリアの後ろに再配置することはできません。バリアの後のコードをバリアの前に再配置します。 メモリ バリアを実行するときは、以前のコードがすべて実行されていること、および実行結果がバリア後のコードに表示されていることを確認してください。 作業メモリ内の変数を強制的にメインメモリにフラッシュします。 他のスレッドの作業メモリ内の変数は無効に設定されるため、メイン メモリから再度読み取る必要があります。3. Volatile とアトミック性
volatile がアトミック性を保証できないことは誰もが知っていますが、なぜアトミック性を保証できないのでしょうか? コードのデモ:package com.github.excellent01; import java.util.concurrent.CountDownLatch; /** * @auther plg * @date 2019/5/19 9:37 */ public class TestVolatile implements Runnable { private volatile Integer num = 0; private static CountDownLatch latch = new CountDownLatch(10); @Override public void run() { for(int i = 0; i < 1000; i++){ num++; } latch.countDown(); } public Integer getNum() { return num; } public static void main(String[] args) throws InterruptedException { TestVolatile test = new TestVolatile(); for(int i = 0; i < 10; i++){ new Thread(test).start(); } latch.await(); System.out.println(test.getNum()); } }
原因分析:
num の操作は 3 つのステップで構成されます:
num をメイン メモリから作業メモリに読み取ります。中作業メモリに1つ追加します追加が完了したら、メインメモリに書き戻します。 これら 3 つのステップはすべてアトミックな操作ですが、これらを合わせてもアトミックな操作ではないため、各ステップは実行中に中断される可能性があります。 このとき num の値が 10 であるとします。スレッド A は変数を自身のワーキング メモリに読み込みます。このとき CPU スイッチが発生します。B も num を自身のワーキング メモリに読み込みます。このとき、スレッド A は変数を自身のワーキング メモリに読み込みます。スレッド B は自身の作業メモリ内の num の値を変更して 11 に変更しますが、この時点ではメイン メモリにリフレッシュされていないため、スレッド A はその値を知りません。 num の値が変更されました。前に述べたように、volatile 変数が変更されると、他のスレッドはそれをすぐに認識します。前提条件として、最初にメイン メモリに更新する必要があります。このとき、他のスレッドは共有変数の値を設定します。彼らが取り組んでいる変数が無効になります。メインメモリにリフレッシュされていないため、A は愚かにも知らず、10 に 1 を加算しました。 したがって、最終的には両方のスレッドがインクリメント操作を実行しましたが、最終的な結果は 1 回だけ加算されました。 これが、volatile がアトミック性を保証できない理由です。volatile の使用シナリオ
volatile の特性によると、順序性と可視性は保証されますが、原子性は保証されないため、volatile はそうでないものにも使用できます。 require atomicity. 、またはアトミック性が保証されている場合: コードデモvolatile boolean shutdownRequested public void shutdown() { shutdownRequested = true; } public void work() { while(shutdownRequested) { //do stuff } }
package com.github.excellent; import java.util.concurrent.ThreadPoolExecutor; /** * 启动线程会被阻塞,flag 从内存读入,会存入寄存器中,下次直接从寄存器取值 * 因此值一直是false * 即使别的线程已经将值更改了,它也不知道 * 加volatile即可。也可以加锁,只要保证内存可见性即可 * @auther plg * @date 2019/5/2 22:40 */ public class Testvolatile { public static boolean flag = false; public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(()->{ for(;;) { System.out.println(flag); } }); Thread thread2 = new Thread(()->{ for(;;){ flag = true; } }); thread1.start(); Thread.sleep(1000); thread2.start(); } }
以上が揮発性キーワードの深い理解の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。