マルチスレッド - シングルトン モード

v2-b6f7ee5f6c7a9e66581bda987710eb4f_b0213

シングルトン パターン~~ シングルトン パターンは一般的なデザイン パターンの 1 つです

デザインパターンとは

チェス、バックギャモン、囲碁を知っていますか? 囲碁を上手にプレイしたい場合は、「チェス レコード」について理解する必要があります。デザイン パターンは、囲碁の「チェス レコード」に似ています。チェスのレコード
、偉人たちはいくつかの共通点を置いた ゲームのシナリオはすべて推定されています。チェスの記録に従ってチェスをプレイすれば、基本的にあなたのチェスのスキルはそれほど悪くはなりません。同様に、ソフトウェア開発には多くの一般的な「問題のシナリオ」があります
。このような問題のシナリオに対して、マスター いくつかの修正ルーチンをまとめましたので、このルーチンに従ってコードを実装すれば、作成するコードはそれほど悪くはありません。
デザイン パターンは、いくつかの典型的なシナリオに対していくつかの典型的なソリューションを提供します。

シングルトンパターン

シングルトンモード => シングルインスタンス (オブジェクト)
~~ Java の既存の構文を巧みに利用することで、特定のクラスのインスタンスを 1 つだけ作成できるという効果を実現しますが、誤って複数のインスタンスを作成すると、コンパイルエラーが報告されます。

シナリオ: JDBC の DataSource のようなクラスなど、実際にはシングルトン モードの使用に非常に適したシナリオが多数あります。

シングルトン パターンの具体的な実装方法は、「hungry man」と「lazy man」の 2 種類に分けられます
注: 実際、Java でシングルトン パターンを実装する方法は数多くありますが、最も一般的なのはこの 2 つです。

ハングリーモード

クラスロード段階では、インスタンスが作成されます(クラスロードは比較的初期の段階です)。この効果は、長い間お腹が空いていて食べ物を見た人のように、「特に熱心な」感覚を人々に与えます。この感情は「飢えた男モード」という鮮やかな名前を与えていますが、もう一つの理由は、後述する「怠け者モード」に対応しているためです。

class Singleton {
    
    
    // 在此处, 先把这个实例给创建出来了
    private static Singleton instance = new Singleton();
    // 被 static 修饰的 Singleton 这个属性和实例无关,而是和类有关
    /*
     * java 代码中的每个类,都会在编译完成后得到.class 文件.
     * JVM 运行是就会加载这个 .class 文件读取其中的二进制指令,并且在内存中
     * 构造出对应的类对象.(形如 Singleton.class) => 
     * */
    /*
     * 由于类对象 在一个 java 进程里,只是有唯一一份的
     * 因此类对象内部的类属性也是唯一一份了
     * */

    // 如果需要使用这个唯一实例, 统一通过 Singleton.getInstance() 方式来获取对象
    public static Singleton getInstance() {
    
    
        return instance;
    }

    // 为了避免 Singleton 类不小心被复制出多份来.
    // 把构造方法设为 private, 在类外面,就无法通过 new 的方式来创建这个 Singleton 实例了
    private Singleton() {
    
    
    }
}

インスタンスが一意であることを確認する方法

  1. 静的操作により、現在のインスタンス属性がクラス属性になります。
    クラス属性はクラス オブジェクト上にあり、クラス オブジェクトが唯一のインスタンスです (インスタンスはクラスの読み込みフェーズ中にのみ作成されます)。
    • 注意: クラス属性とクラス オブジェクトの間には 1 対 1 の対応があります。つまり、複数のクラス オブジェクトがある場合、複数のクラス属性も存在します。この場合、クラス オブジェクトはシングルトンではなくなります。
  2. コンストラクターはプライベートに設定されています。New は外部コードでは使用できません。

クラスロードフェーズ

Java プログラムを実行するには、Java プロセスが対応する .class ファイルを見つけて読み取ることができるようにする必要があります。ファイルの内容は読み取られ、解析され、クラス オブジェクトに構築されます...この一連のプロセス操作はクラス ロードと呼ばれます。

遅延モードの実装

このインスタンスは、クラスがロードされたときに作成されるのではなく、実際に初めて使用されるときに作成されます (使用されない場合は作成されません => "lazy")。 「勤勉
」という言葉は軽蔑的な言葉です~~ 「効率」という観点から見ると、怠け者モードは飢えた男モードよりも優れています!!!

遅延モードのシングルスレッドバージョン

class SingletonLazy {
    
    
    private static SingletonLazy instance = null;

    public static SingletonLazy getInstance() {
    
    
        if (instance == null) {
    
    
            instance = new SingletonLazy();
        }
        return instance;
    }

    private SingletonLazy() {
    
    
    }
}

上で書いたハングリーモードとレイジーモードがマルチスレッド環境で呼び出された場合getInstance、それらはスレッドセーフですか?

画像-20231001135552693

if (instance == null) { instance = new SingletonLazy(); } return instance;

if (instance == null) {
    
    
       instance = new SingletonLazy();
   }
   return instance;

画像-20231001142343309

先ほどのスレッド セーフティの問題の本質は、読み取り、比較、書き込みの 3 つの操作がアトミックではないということです。そのため、t2 によって読み取られた値は、t1 が書き込む時間がなかった値である可能性があります (ダーティ リード)。 )

Lazy モード - マルチスレッド バージョン

public static SingletonLazy getInstance() {
    
    
    synchronized (SingletonLazy.class) {
    
    
        if (instance == null) {
    
    
            instance = new SingletonLazy();
        }
    }
    return instance;
}

注: ロック後は、この時点の読み取り操作と変更操作が統合されることが保証されます。
画像-20231001151835949

Lazy モード マルチスレッド バージョン (改善)

 public static SingletonLazy getInstance() {
    
    
     synchronized (SingletonLazy.class) {
    
    
         if (instance == null) {
    
    
             instance = new SingletonLazy();
        }
     }
     return instance;
 }

上記のコードでは、getlnstance を使用するたびにロックする必要があります (ロック操作にはオーバーヘッドがあります)。
ここで疑問が生じます:
本当に毎回ロックする必要がありますか? いいえ、ここでのロックはオブジェクトから新しいオブジェクトが出てくる前にのみ追加されます。ロックは必要です。新しい
オブジェクトが完了すると、続いて getlnstance が呼び出され、その値がこのときのインスタンスは空ではない必要があるため、return は直接トリガーされます。
比較操作と return 操作に相当します。どちらの操作も読み取り操作です。この時点ではロックしなくても問題ありません。


解決策: 上記の議論に基づいて、オブジェクトが作成されていない場合はロックされ、
オブジェクトが作成されている場合はロックされないという判断を上記のコードに追加できます。

public static SingletonLazy getInstance() {
    
    
    if (instance == null) {
    
     // 此处不再是无脑加锁了而是满足了特定条件之后,才真正加锁.
        synchronized (SingletonLazy.class) {
    
    
            if (instance == null) {
    
    
                instance = new SingletonLazy();
            }
        }
     }
    return instance;
}

分析: これら 2 つの条件の間にロックがない場合、2 つの連続する if は意味がありません。
しかし、ロックがある場合は必ずしもそうではありません。ロック操作によりスレッドがブロックされる可能性があります。ロックが終了すると、2 番目の if が終了します。注:
最初の if条件は、ロックするかどうかを決定する役割を果たします

、2 番目の if 条件は、オブジェクトを作成するかどうかを決定します。これら 2 つの if 条件の目的はまったく異なりますが、偶然の一致により、コードは同じです。

上記の遅延モード コードには、メモリの可視性の問題と命令の並べ替えに関する解決すべき問題がまだ残っています。

可視性の問題:
多数のスレッドがあり、すべて getInstance に行くと仮定します。この時点で、最適化されるリスクはありますか?
(最初の読み取りのみが実際のメモリ読み取りであり、後続のレジスタ/キャッシュの読み取りは行われます)

命令の並べ替えの問題

instance new Singleton();

1. メモリ空間を適用する
2. コンストラクタ メソッドを呼び出して、このメモリ空間を適切なオブジェクトに初期化する
3. メモリ空間のアドレスをインスタンス参照に割り当てる3 つのステップに分割します。

通常は123の順番で実行されますが、コンパイラが別の処理を行っているため、プログラムの効率向上やコードの実行順序を調整するため、123の順番が132になる場合があります
。 132 と 132 の間に本質的な違いはありませんが、マルチスレッド環境では問題が発生します!!!
t1 が 132 の手順に従って実行されたとします。t1 が 13 まで実行された後、2 が実行される前に、が CPU から切り出され、t2 が実行されます (t1 が実行されるとき 13 が完了した後、t2 から見ると、ここの参照は空ではありません)。この時点では、t2 はインスタンスの参照を直接返すのと同等であり、次のことを試みる可能性があります。参照内の属性を使用しますが、t1 の 2 つの操作はまだ実行が完了していないため、t2 が取得したのは不正なオブジェクトであり、まだ構築されていない不完全なオブジェクトです。

揮発性の

volatile には 2 つの機能があります。

  1. メモリ可視性の解決
    2. 命令の並べ替えを無効にする

完全なシングルトン モード (遅延モード) コード

class SingletonLazy {
    
    
    private volatile static SingletonLazy instance = null;// 1

    public static SingletonLazy getInstance() {
    
    
        if (instance == null) {
    
    // 2
            synchronized (SingletonLazy.class) {
    
    // 3
                if (instance == null) {
    
    
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }

    private SingletonLazy() {
    
    
    }
}

おすすめ

転載: blog.csdn.net/m0_73740682/article/details/133466413