Java同時16:volatileキーワードの2つの使い方-使い捨てステータスフラグ、二重チェック単例モード


[ハイパーリンク:Java同時学習シリーズ-緒論]
volatileキーワードは、前の章で複数回言及されています.
  • 『Java同時11:Javaメモリモデル、命令再配置、happens-before原則』:volatileはロックプレフィックス方式のメモリバリア擬似タイプで実現される.
  • 『Java同時14:同時三特性-可視性定義、可視性問題と可視性保証技術』:volatileキーワードはメモリバリアをマークすることによって変数の可視性を保証する.

  • この章では主にvolatileキーワードの2つの実際の使い方について説明します.

    1.volatileの概要

  • volatile、すなわち可変であり、Javaで変数が可変変数であることを識別する.
  • volatileは軽量級のsynchronizedと見なすことができ、synchronizedに比べて符号化が簡単で、リソースオーバーヘッドが少なく、同様の実現機能も限られている.
  • volatileは変数の可視性を保証できるが、変数の原子性と秩序性を保証することはできない.
  • volatileを使用する前提は、原子性および秩序性の影響を受けない:変数状態は、任意のプログラムの他の状態とは完全に独立している.

  • 2.volatileの2つの使い方


    本稿では、volatileでよく見られる2つの使い方についてのみ学習します.
  • 使い捨て状態フラグ
  • 単例モード:二重検査単例モード
  • 2.1.いちじじょうたいひょうしき


    適用シーン:重要な使い捨てイベントが発生したことを示すブール変数を使用してステータスをマークします.たとえば、フラグ構成の初期化が完了し、あるサービスがサービスを停止したことを示すなどです.
    エンコーディングシーン:
  • コーヒーマシンは、お客様のためにコーヒーを作ることができます.
  • このコーヒーマシンには閉じるボタンがあり、閉じるボタンを押すと、コーヒーマシンはサービスを停止します.

  • 2.1.1.volatileキーワードを使用しない


    ステータスフラグビット:
    /**
     *  
     */
    private static boolean shutdown = false;

    コーヒーマシン:
    /**
     * 

    Title:

    * * @author 2018/3/16 13:58 */
    static class CoffeeMaker { /** * */ public static void shutdown() { shutdown = true; System.out.println(" ..."); } /** * */ public static void makeCoffee(String name) { System.out.println(" ..."); while (!shutdown) ; System.out.println(" , !"); } }

    コーヒーを作るとコーヒーマシンを閉じる:
    // 
    new Thread(() -> {
        CoffeeMaker.makeCoffee(Thread.currentThread().getName());
    }).start();
    Thread.sleep(100);
    // 
    new Thread(() -> {
        CoffeeMaker.shutdown();
    }).start();

    実行結果:
     ...
     ...

    結果分析:
  • スレッド2番目のスレッドは、この状態をtrueに設定するかどうかをオフにしますが、1番目のスレッドはこの状態の変化に気づかず、コーヒーマシンが停止できません.
  • これは,タスクアクションを取らない場合,共有変数の修正が他のスレッドに対して必ずしも見られないためである.

  • 2.1.2.volatileキーワードの使用


    上のコードを小さな変更します.この状態を閉じてvolatileキーワードでマークするかどうか:
    /**
     *  
     */
     private volatile static boolean shutdown = false;

    次に、テストコードを再実行します.実行結果は次のとおりです.
     ...
     ...
     , !

    2.1.3.使い捨て状態フラグの使い方の分析について


    使い捨てステータスフラグを使用するキー:
  • 1.ステータスフラグのステータス変換は原子操作です.たとえば、上記のコードでは、Javaでは原子的な操作であるブールタイプに値を付けます.
  • 2.一時的な状態遷移のみです.上記のコードでは、ステータスフラグビットはfalseからtrueに変換されるだけで、trueからfalseへの変換などは継続されません.このような転換の使い捨ては秩序性問題の発生を根絶した.

  • 上記の2つのキーは,実は原子性と秩序性の保証であることがわかる.
    すなわち,volatileで同時プログラミングを行う場合,コードの原子性と秩序性を他の手段で保証する必要がある.

    2.2.ダブルチェック


    二重検査は一例モードの実現方式であり,一例モードについてはここではあまり紹介しない.

    2.2.1.volatileを付けない二重検査モード

    /**
     * 

    -- volatile

    * * @author hanchao 2018/3/17 19:14 **/
    static class DoubleCheckSingleton { private static DoubleCheckSingleton instance = null; private DoubleCheckSingleton() { } public static synchronized DoubleCheckSingleton getInstance() { if (instance == null) {//0.null synchronized (DoubleCheckSingleton.class) { if (instance == null) { // instance = new DoubleCheckSingleton();//1. 2. } } } return instance;//3. } }

    このパターンには問題があります.
  • instance = new DoubleCheckSingleton(); この操作は原子的ではない.

  • この操作は次のように分けられます.
  • Heapでアドレスを開き、オブジェクト初期化を行う:new DoubleCheckSingleton()
  • Heapで初期化が完了したDoubleCheckSingletonオブジェクトアドレスをThread Stackのオブジェクトへinstanceを参照します.

  • この2つのステップの動作は、単一スレッドにおける1−>2および2−>1の実行結果が同じであるため、命令再拍後に2−>1の順序である可能性がある.
    このコマンドの再ショットは、単一スレッドでは問題ありませんが、マルチスレッドでは問題がある可能性があります.
  • スレッドAのgetInstance()メソッドでの実行順序は、0->2->1->3であり、現在はinstanceがすでに実行されている2ステップ目に実行されています!=nullです.
  • スレッドBはgetInstance()メソッドに入り、0でinstanceが検出されました!=nullなので、手順4:instanceオブジェクトに戻ります.
  • スレッドBは、instance.getName()などの後続の動作を継続する.
  • でスレッドAはまだ第1ステップの初期化動作を実行しているが、instanceアプリケーションが実行する実際のアドレスはnull値である.
  • instance.getName()などの操作で、NullPointerException異常が報告されます.

  • 2.2.2.volatileの二重検査モードを追加


    JAvaのメモリモードは継続的に改善されており、jdk 5では次のように説明されています.
    Updates for J2SE 5.0 (aka 1.5, Tiger) In particular, double-check idioms work in the expected way when references are declared volatile.
    つまりJDK 1.5以降のバージョンでは、オブジェクト参照をvolatileと宣言することで、二重チェックモードを正常に使用できます.
    volatileの二重検査モードを追加するコードは以下の通りです.
    /**
     * 

    -- volatile

    * * @author hanchao 2018/3/17 19:10 **/
    static class DoubleCheckedVolatileSingleton { // volatile private volatile static DoubleCheckedVolatileSingleton instance = null; public static DoubleCheckedVolatileSingleton getInstance() { if (instance == null) { synchronized (DoubleCheckedVolatileSingleton.class) { if (instance == null) { instance = new DoubleCheckedVolatileSingleton(); } } } return instance; } }

    参考文献


    [1]Java同時プログラミング:volatileキーワード解析[2]Javaでのダブルチェック[3]ダブルチェックロックが無効になったのは、オブジェクトの初期化が原子操作ではないためですか?