前書き:
シングルトン モードを実装するには、ハングリー モード、レイジー モード、静的内部クラス、列挙型など、インタビュアーが「なぜ volatile をシングルトン モードに追加する必要があるのですか?」と尋ねたとき、彼はなぜ Lazy モードのプライベート変数を volatile に追加する必要があるのかについて言及しています。
遅延モードとは、オブジェクト作成の遅延読み込み方式を指し、オブジェクトはプログラムの起動時には作成されず、実際に初めて使用されるときにのみ作成されます。
なぜ volatile を追加する必要があるのか説明していただけますか?まず、遅延モードの具体的な実装コードを見てみましょう:
public class Singleton { // 1.防止外部直接 new 对象破坏单例模式 private Singleton() {} // 2.通过私有变量保存单例对象【添加了 volatile 修饰】 private static volatile Singleton instance = null; // 3.提供公共获取单例对象的方法 public static Singleton getInstance() { if (instance == null) { // 第 1 次效验 synchronized (Singleton.class) { if (instance == null) { // 第 2 次效验 instance = new Singleton(); } } } return instance; } }
上記のコードからわかるように、スレッドの安全性と高いパフォーマンスを確保するために、コード内で if と synchronized が 2 回使用されています。プログラムの実行を確実にするため。では、スレッドの安全性を確保するためにすでに synchronized が行われているのに、なぜ変数に volatile を追加する必要があるのでしょうか?この問題を説明する前に、まず前提条件となる知識を理解する必要があります: volatile の用途は何ですか?
volatile には 2 つの主な機能があります。1 つ目はメモリの可視性の問題を解決し、2 つ目は命令の並べ替えを防止します。
いわゆるメモリ可視性問題とは、複数のスレッドが同時に変数を操作する場合を指します。1 つのスレッドが変数の値を変更した後、他のスレッドがスレッド 変数の変更は認識できません。これはメモリの可視性の問題です。 volatile を使用すると、メモリ可視性の問題を解決できます。 次のコードのように、volatile が追加されていない場合、その実装は次のようになります。
private static boolean flag = false; public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { // 如果 flag 变量为 true 就终止执行 while (!flag) { } System.out.println("终止执行"); } }); t1.start(); // 1s 之后将 flag 变量的值修改为 true Thread t2 = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("设置 flag 变量的值为 true!"); flag = true; } }); t2.start(); }
実行結果上記のプログラムは次のようになります:
ただし、上記のプログラムを N 回実行してもまだ終了していません。これは、スレッド 2 が変更されたことを示しています。フラグ変数、スレッド 1 は変数の変更を認識しませんでした。
次に、フラグに volatile を追加してみます。実装コードは次のとおりです。
public class volatileTest { private static volatile boolean flag = false; public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { // 如果 flag 变量为 true 就终止执行 while (!flag) { } System.out.println("终止执行"); } }); t1.start(); // 1s 之后将 flag 变量的值修改为 true Thread t2 = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("设置 flag 变量的值为 true!"); flag = true; } }); t2.start(); } }
上記プログラムの実行結果は次のようになります。
上記の実行結果から、volatile を使用するとプログラム内のメモリ可視性の問題を解決できることがわかります。
命令の並べ替えとは、プログラムの実行中に、コンパイラまたは JVM がプログラムの実行パフォーマンスを向上させるために命令の順序を頻繁に並べ替えることを意味します。命令の並べ替えの本来の設計意図は確かに非常に優れており、シングル スレッドでも大きな役割を果たすことができますが、マルチスレッドでは命令の並べ替えを使用するとスレッドの安全性の問題が発生する可能性があります。
いわゆるスレッド セーフティの問題とは、プログラムの実行結果が期待と一致しないことを指します。たとえば、予期した正しい結果が 0 であるにもかかわらず、プログラムの実行結果が 1 である場合、これはスレッド セーフティの問題です。
volatile を使用すると命令の並べ替えを禁止できるため、複数のスレッドで実行するときにプログラムを正しく実行できます。
話題に戻りますが、 シングルトン モードでは volatile を使用します。主な理由は、volatile は命令の並べ替えを禁止し、それによってプログラム の通常の動作を確保できるからです。ここで読者の中には、スレッドの安全性を確保するために synchronized がすでに使用されているのではないかという疑問を持つ人もいるかもしれません。では、なぜ volatile を追加するのでしょうか?次のコードを見てください:
public class Singleton { private Singleton() {} // 使用 volatile 禁止指令重排序 private static volatile Singleton instance = null; public static Singleton getInstance() { if (instance == null) { // ① synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); // ② } } } return instance; } }
上記のコードに注目してください。コードの 2 行の①と②にマークを付けました。プライベート変数に volatile を追加するのは、主に②の実行時、つまり「instance = new Singleton()」の実行時に命令が並べ替えられるのを防ぐためです。実際の実行は次の 3 つのステップに分かれています。
メモリ空間を作成します。
#オブジェクト Singleton をメモリ空間で初期化します。
#メモリ アドレスをインスタンス オブジェクトに割り当てます (この手順を実行すると、インスタンスは null に等しくなくなります)。
volatile が追加されていない場合、スレッド 1 は上記のコードの②を実行するときに命令の並べ替えを実行し、元の実行順序を変更する可能性があります。 1、2、3 を 1、3、2 に並べ替えます。ただし、特殊な状況では、スレッド 1 がステップ 3 を実行した後、スレッド 2 が来て上記のコードの最初のステップを実行すると、インスタンス オブジェクトは null ではないが、スレッド 1 はまだオブジェクトのインスタンス化を完了していないと判断されます。スレッド 2 はインスタンス化された「半分」のオブジェクトを取得し、プログラム実行エラーが発生するため、プライベート変数に volatile が追加されます。
以上がJava シングルトン モードに volatile キーワードを追加する必要があるのはなぜですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。