デザインパターン-シングルトンパターンを本当に知っていますか?

デザインパターン(デザインパターン)は、繰り返し使用され、ほとんどの人に知られているコードデザインエクスペリエンスのセットです。デザインパターンの使用は、コードを再利用可能にし、他の人が理解しやすくし、コードの信頼性を確保することです。デザインパターンが他の人やシステムにとって双方にメリットがあることは間違いありません。デザインパターンはコードの準備を真のエンジニアリングにします。デザインパターンは、建物のレンガのように、ソフトウェアエンジニアリングの基礎です。プロジェクトでデザインパターンを合理的に使用することで、多くの問題を完全に解決できます。各パターンには、現在対応する原則があります。各パターンは、私たちの周りで再発する問題と問題の核心を表しています。解決策、それが可能である理由です。広く使用されます。簡単に言えば、パターンは、特定のシナリオにおける特定のタイプの問題に対する一般的な解決策です。

一般的に、23のデザインパターンは作成パターン構造パターン行動パターンの3つのカテゴリに大別できます

作成モード:オブジェクトのインスタンス化プロセスを分離するために使用される、オブジェクトのインスタンス化のモード

構造モード:クラスまたはオブジェクトを組み合わせて、より大きな構造を形成します。

行動パターン:クラスとオブジェクトがどのように相互作用し、責任とアルゴリズムを分割するか。

今日は、最初に作成モードのシングルトンモードについて説明します

シングルトンパターンとは

シングルトンモードも面接でよくある問題です。多くの人がそれを知っていますが、多くの人はすべての書き方、そして書き方の問題と解決策に精通していません。

シングルトンモードには、次の3つの典型的な特徴があります。1。インスタンスは1つだけです。2.自己インスタンス化。3.グローバルアクセスポイントを提供します。

したがって、システムで必要なインスタンスオブジェクトが1つだけ、またはシステムで許可されているパブリックアクセスポイントが1つだけで、このパブリックアクセスポイント以外のアクセスポイントからインスタンスにアクセスできない場合は、シングルトンモードを使用できます。

シングルトンモードの主な利点は、システムリソースを節約し、システム効率を向上させると同時に、顧客のアクセスを厳密に制御できることです。システム内にインスタンスが1つしかないため、シングルトンクラスが過負荷になり、「単一責任の原則」に違反し、抽象クラスがないため、拡張が難しいためと考えられます。

シングルトンモードのさまざまな書き方

1.空腹のチャイニーズスタイル(推奨)

このようにしてprivate static final Singleton1 INSTANCE = new Singleton1()、静的変数が定義されます。JVMがクラスをロードすると、シングルトンがインスタンス化されます。クラスファイルは1回しかロードされないため、JVMはスレッドセーフを保証できます。

同時に、これprivate Singleton1()は民営化されたコンストラクターであり、このクラスが他のクラスで新しくならないようにすることができます。

外部クラスがこのインスタンスを取得する場合は、public static Singleton1 getInstance()メソッドを介して取得する必要があります

package com.wuxiaolong.design.pattern.singleton;

/**
 * Description: 饿汉式
 * @author 诸葛小猿
 * @date 2020-08-05
 */
public class Singleton1 {
    
    

    /**
     * 静态变量 启动时自动加载
     */
    private static final Singleton1 INSTANCE = new Singleton1();

    /**
     * 私有化构造器,不能new
     */
    private Singleton1(){
    
    }

    /**
     * 对外暴露一个方法,获取同一个实例
     */
    public static Singleton1 getInstance(){
    
    
        return INSTANCE;
    }

    /**
     * 测试
     */
    public static void main(String[] args) {
    
    
        Singleton1 s1 = Singleton1.getInstance();
        Singleton1 s2 = Singleton1.getInstance();

        if(s1 == s2){
    
    
            System.out.println("s1和s2是内存地址相同,是同一个实例");
        }
    }

}

このアプローチの唯一の問題は、使用されない場合にインスタンス化されることです。欠点が見つからなければ、それは実際には問題ではありません。

この方法はシンプルで実用的であり、実際のプロジェクトで使用することを強くお勧めします

2.怠惰な男のスタイル1

怠惰な男のスタイルは、それが使用されているかどうかに関係なく、空腹の男のスタイルをロードするという上記の問題を解決することです。

いわゆる遅延読み込みとは、使用時に初期化され、使用されていないときには読み込まれません。

private static volatile Singleton2 INSTANCE定義された変数を使用しますが、インスタンスは初期化しません。変数はgetInstance()、メソッドが呼び出されたときに初期化されます。

package com.wuxiaolong.design.pattern.singleton;

import java.util.HashSet;
import java.util.Set;

/**
 * Description: 懒汉式一
 * @author 诸葛小猿
 * @date 2020-08-05
 */
public class Singleton2 {
    
    
    /**
     * 先不初始化   使用volatile的原因见底部
     */
    private static volatile Singleton2 INSTANCE;

    /**
     * 私有化构造器,不能new
     */
    private Singleton2(){
    
    }

    /**
     * 对外暴露一个方法,获取同一个实例。只有实例不存在时才初始化
     * 问题:多线程同时访问getInstance时,可能会new出多个实例
     */
    public static Singleton2 getInstance(){
    
    
        if(INSTANCE == null){
    
    

//            try {
    
    
//                Thread.sleep(10);
//            }catch (Exception e){
    
    
//             e.printStackTrace();
//            }

            INSTANCE = new Singleton2();
        }
        return INSTANCE;
    }

    /**
     * 测试
     * 测试时可以开启 getInstance()方法中的sleep
     */
    public static void main(String[] args) {
    
    

        for(int i=0;i<100;i++){
    
    
            new Thread(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    Singleton2 temp = getInstance();
                    // 打印出内存地址,如果内存地址不一样,则生成了多个实例
                    System.out.println(temp.toString());
                }
            }).start();
        }

    }
}

上記の怠惰な男を使用すると、他の多くの問題も引き起こします。マルチスレッドアクセスの場合、getInstance()スレッドセーフの問題が発生し、複数のインスタンスが生成されます。上記のmainテストを使用してテストできます

スレッドセーフの問題を解決するために、同期されたロックを使用できます(ロックする静的メソッドはこのクラスをロックすることです)。

    public static synchronized Singleton2 getInstance(){
    
    
        if(INSTANCE == null){
    
    
            INSTANCE = new Singleton2();
        }
        return INSTANCE;
    }

しかし、これはパフォーマンスの問題をもたらします。パフォーマンスを向上させるには、コードブロックにロックを追加します。

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

しかし、このアプローチにはまだスレッドセーフの問題があります。複数のスレッドが同時にifに入ることができます。同期されたコードブロックを同時に実行できるのは1つのスレッドだけですが、ifに入るスレッドはすべてインスタンス化されます。

3.怠惰な男スタイル2

上記の怠惰なもののスレッドセーフの問題を解決するために、誰かが「スマート」なテクニックを思いついた:ダブルチェックロック(ダブルチェックロック)

package com.wuxiaolong.design.pattern.singleton;

/**
 * Description: 懒汉式二
 *
 * @author 诸葛小猿
 * @date 2020-08-05
 */
public class Singleton3 {
    
    
    /**
     * 先不初始化
     */
    private static volatile Singleton3 INSTANCE;

    /**
     * 私有化构造器,不能new
     */
    private Singleton3(){
    
    }

    /**
     * 对外暴露一个方法,获取同一个实例
     * 内部双重判断,使用两次INSTANCE == null的判断
     */
    public static Singleton3 getInstance(){
    
    
        if(INSTANCE == null){
    
    
            synchronized (Singleton3.class){
    
    
                // synchronized块内部,再判断一次是否为空
                if(INSTANCE == null){
    
    
                    INSTANCE = new Singleton3();
                }
            }
        }
        return INSTANCE;
    }

    /**
     * 测试
     */
    public static void main(String[] args) {
    
    

        for(int i=0;i<100;i++){
    
    
            new Thread(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    Singleton3 temp = getInstance();

                    // 打印出内存地址,如果内存地址不一样,则生成了多个实例
                    System.out.println(temp.toString());
                }
            }).start();
        }
    }
}

糸の安全性を保証できるように、二重チェックはsynchronized内側と外側を別々判断INSTANCE == nullすることです。

    public static Singleton3 getInstance(){
    
    
        // 第一次检查
        if(INSTANCE == null){
    
    
            synchronized (Singleton3.class){
    
    
                // synchronized块内部,第二次检查
                if(INSTANCE == null){
    
    
                    INSTANCE = new Singleton3();
                }
            }
        }
        return INSTANCE;
    }

上記の最初のINSTANCE == nullに必要ですか?もちろん、最初の判断がない場合は、すべてのスレッドが入ってくるときにロックされます。これは非常にパフォーマンスが高くなります。最初の判断では、多くのスレッドがロックされません。

これは最も完璧な書き込み方法の1つであり、実際のプロジェクトで使用することをお勧めします。二重の判断を使用すれば、スレッドセーフの問題はありません。

しかし、個人的には、それは本当に不要です。空腹の男モードを使用するだけです。これは乙女座によって推奨されるかもしれません。

4.怠惰な男性タイプ3(推奨)

ここprivate static class InnerSingletonは静的内部クラスが使用されます。これにより、遅延読み込みが保証されるだけでなく、スレッドの安全性も保証されます。

ここで遅延ロードを行う理由は、jvmがSingleton4.classをロードするときに、静的内部クラスInnerSingleton.classgetInstance()をロードせず、呼び出しのみが静的内部クラスInnerSingleton.classをロードするためです。

ここでのスレッドセーフはJVMによって保証されています。jvmがSingleton4.classをロードするとき、それは1回だけロードされます。したがって、InnerSingleton.classは1回だけロードされるため、INSTANCEは1回だけインスタンス化されます。

package com.wuxiaolong.design.pattern.singleton;

/**
 * Description: 
 * 既保证了懒加载,也保证了线程安全。
 * @author 诸葛小猿
 * @date 2020-08-05
 */
public class Singleton4 {
    
    

    /**
     * 私有化构造器,不能new
     */
    private Singleton4(){
    
    }

    /**
     * 使用静态内部类,jvm加载Singleton4.class的时候,不会加载静态内部类InnerSingleton.class
     * 只有调用getInstance()才会加载静态内部类InnerSingleton.class
     */
    private static class InnerSingleton{
    
    
        private static final Singleton4 INSTANCE = new Singleton4();
    }

    /**
     * 对外暴露一个方法,获取同一个实例
     * 返回的是静态内部类的成员变量,这个变量在调用的时候才会初始化
     */
    public static Singleton4 getInstance(){
    
    
        return InnerSingleton.INSTANCE;
    }


    /**
     * 测试
     */
    public static void main(String[] args) {
    
    
        for(int i=0;i<100;i++){
    
    
            new Thread(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    Singleton4 temp = getInstance();
                    // 打印出内存地址,如果内存地址不一样,则生成了多个实例
                    System.out.println(temp.toString());
                }
            }).start();
        }
    }
}

これは、壁のひび割れに推奨される完璧なソリューションでもあります。

5.アルティメットモード-列挙

Java開発は、本が「EffectiveJava」と呼ばれることを知っています。「EffectiveJava」はJoshuaBlochによって書かれました。彼はJavaCollection Frameworkの創設者であり、JDK5.0言語の機能強化や受賞歴のあるJavaCollection Frameworkなど、多くのJavaプラットフォーム機能の設計と実装を主導しています。彼は2004年6月にSUNを去り、GoogleのチーフJavaアーキテクトになりました。さらに、彼は本「EffectiveJava」で権威あるJolt賞を受賞しました。

この本では、列挙を使用して、究極の解決策が示されています。

package com.wuxiaolong.design.pattern.singleton;

/**
 * Description: 
 *  在《Effective Java》这本书中,给出了终极解决方案,使用枚举。
 *  使用枚举这种方式,不仅可以解决线程同步问题,还可以防止反序列化。
 * @author 诸葛小猿
 * @date 2020-08-05
 */
public enum Singleton5 {
    
    

    INSTANCE;

    /**
     * 测试
     */
    public static void main(String[] args) {
    
    
        for(int i=0;i<100;i++){
    
    
            new Thread(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    // 打印出内存地址,如果内存地址不一样,则生成了多个实例
                    System.out.println(Singleton5.INSTANCE.toString());
                }
            }).start();
        }
    }

}

このように列挙を使用すると、スレッド同期の問題を解決できるだけでなく、逆シリアル化を防ぐこともできます。

シングルトンモードがシリアル化と逆シリアル化を妨げるのはなぜですか?以前の形式では、クラスは引き続きリフレクション(classForName)の形式でインスタンス化できます。列挙型には構築メソッドがないため、リフレクションによってクラスをインスタンス化することはできません。

文法的にはこの書き方が一番完璧ですが、あまり好きではないので、定数と同じ定義だと思います。

揮発性キーワードの変更を使用する理由

空腹の人モードでは、メンバー変数INSTANCEはvolatileキーワードで変更されますが、その理由は何ですか?

volatileキーワードで変更しないと、次のような問題が発生します。

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

**スレッドが2行目まで実行されると、INSTANCEがnullではないのに、INSTANCEが指すオブジェクトがまだ初期化されていない場合があります。**主な理由は再注文です。並べ替えとは、プログラムのパフォーマンスを最適化するために、コンパイラとプロセッサが命令シーケンスを並べ替える手段を指します。
コードの5行目はオブジェクトを作成します。このコード行は、次の3つの操作に分割できます。

memory = allocate();  // 1:分配对象的内存空间
ctorInstance(memory); // 2:初始化对象
instance = memory;  // 3:设置instance指向刚分配的内存地址

ルートはコード内で2から3の間にあり、並べ替えることができます。例えば:

memory = allocate();  // 1:分配对象的内存空间
instance = memory;  // 3:设置instance指向刚分配的内存地址
// 注意,此时对象还没有被初始化!
ctorInstance(memory); // 2:初始化对象

これはシングルスレッド環境では問題ではありませんが、マルチスレッド環境では問題が発生します。2番目と3番目のステップの命令が並べ替えられた後、Aスレッドは上記の同期コードブロックに入りINSTANCE = new Singleton3()、行Inを実行します。 3番目の命令instance = memoryは、Bスレッドが入るとINSTANCE != null、まだ初期化されていないオブジェクトを取得します。

2番目と3番目のステップでの命令の並べ替えは、スレッドAの最終結果には影響しませんが、スレッドBはインスタンスが空ではないと判断し、初期化されていないオブジェクトにアクセスします。
**したがって、スレッドセーフな遅延初期化を実現できるのは、わずかな変更(インスタンスを揮発性として宣言)のみです。** volatileキーワードで変更された変数は並べ替えが禁止されているためです。

公式アカウントをフォローし、「java-summaryと入力してソースコードを取得します。

終わった、それを一日と呼んでください!

[知識の普及、価値の共有]、あなたの注意とサポートに感謝します、私は[ Zhuge Xiaoyuan ]、ためらいに苦しんでいるインターネット移民労働者です。

おすすめ

転載: blog.csdn.net/wuxiaolongah/article/details/107851003