JAva可視性の問題、メモリバリア、volatileキーワードの理解


目次
  • 前言
  • 指令並び替え
  • メモリバリア
  • volatile

  • 前言
    まずコードを見てみましょう
    public class Test1 {
    
        static boolean stop = false;
    
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread(()->{
                while(true) {
                    if(stop) {
                        return;
                    }
                }
            });
            t1.start();
            Thread.sleep(1000);
            stop = true;
        }
    }
    

    このコードのメインスレッドがブロックされてから1秒後にstopの値がtrueに変更され、理論上のスレッドはwhileループを終了しますが、実際にはこのプログラムは永遠に停止しません.
    理由は次のとおりです.
    ハードウェアレベルでは、CPUのキャッシュ、CPUのデフォルトの3つのキャッシュ
    すなわち、CPUコアA、Bがstopをロードすると、まずstopの値をコアのキャッシュにロードし、Aがtrueに変更された場合、すぐにstopの値をメインメモリにリフレッシュすることはなく、CPUが適切なタイミングでメモリに値をリフレッシュする時間は不確定である.コアBは、自分のキャッシュにstopの値が存在するため、メインメモリの値が更新されても、コアBは自分のキャッシュの値のみを読み出す.これが可視性の問題であり、可視性の問題の原因はCPUがCPUの処理効率を向上させるため、CPUはメモリバリアを提供して私たち自身が業務上の非可視問題を解決するためである.
    めいれいさいはいち
    public class Test1 {
        int a = 0;
        int b = 0;
        
        void set() {
            a = 1;
            b = 1;
        }
    
        void get() {
            while(b == 1) {
                assert (a == 1); // 1       
            }
        }
    }
    

    このコードは、位置1で異常を放出する可能性があります.すなわち、b=1の場合、aは1に等しくない可能性があります.
    理由は以下の通りである:CPUは単一スレッド条件下でメインフローの結果に影響を与えない場合、我々の命令を最適化することができ、すなわち、我々の命令を並べ替える可能性がある、すなわちa=1である.b=1;CPU命令によりb=1に並べ替えることができる.a=1;
    メモリバリア
    メモリバリアは、ハードウェアレベルで命令の並べ替えを禁止できる機能です.
    X 86のmemory barrier命令はlfence(リードバリア)sfence(ライトバリア)mfence(フルバリア)を含む
  • Store Memory Barrier(ライトバリア)は、プロセッサにライトバリアの前のストレージキャッシュ(store bufferes)に格納されているすべてのデータがメインメモリに同期することを伝え、簡単に言えば、ライトバリアの前の命令の結果がバリアの後の読み取りまたは書き込みに可視になる
  • である.
  • Load Memory Barrier(リードバリア)、プロセッサのリードバリア後のリード動作は、いずれもリードバリア後に実行される.書き込みバリアと連携することにより、書き込みバリアの前のメモリ更新は、読み出しバリアの後の読み出し動作に対して可視となる
  • .
  • Full Memory Barrier(フルバリア)は、バリア前のメモリ読み書き動作の結果がメモリにコミットされたことを確認した後、バリア後の読み書き動作
  • を実行する.
    volatile
    Javaレベルでvolatileキーワードが提供され、volatileが変数を修飾すると、キー位置にバリアを作成して命令の並べ替えを阻止できます.
    次のコード
    public class Test1 {
    	//     a
        volatile int a = 0;
        int b = 0;
        
        void set() {
            a = 1;
            // storeMemoryBarrier()   ,     ( a          )
            b = 1;
        }
    
        void get() {
            while(b == 1) {
            	// Load Memory Barrier()   ,   a         a  
                assert (a == 1); 
            }
        }
    }
    

    前述の解析から,可視性問題にはキャッシュによる可視性問題と命令再ソートの2つの要因があることが分かった.
    volatileの主な役割
  • メモリバリア阻止命令を使用して
  • を並べ替える.
  • メモリバリアを使用する、書き込みバリアはデータをメインメモリに書き込み、読み取りバリアは直接メインメモリから値
  • を取得する.
    つまりvolatileを使用して可視性の問題を解決する