デザインパターン_クリエイティブパターン_《シングルトンパターン》

デザインパターン_クリエイティブパターン_《シングルトンパターン》

ダークホースプログラマによるJavaデザインパターンの詳細解説、23のJavaデザインパターン(図表+フレームワークソースコード解析+実戦)をノートとしてまとめています

作成パターンの主な焦点は「オブジェクトをどのように作成するか」であり、その最大の特徴は「オブジェクトの作成と使用の分離」です。

これにより、システムの結合が軽減され、ユーザーはオブジェクト作成の詳細に注意を払う必要がなくなります。

作成パターンは次のように分類されます。

  • シングルトンパターン
  • ファクトリメソッドパターン
  • 抽象的な工場パターン
  • プロトタイプパターン
  • ビルダーモード

シングルトンデザインパターン

シングルトン パターン (Singleton Pattern) は、 Java の最も単純な設計パターンの 1 つです。このタイプのデザイン パターンは创建型模式、オブジェクトを作成するための最適な方法を提供します。

このパターンには、単一のオブジェクトのみが作成されるようにしながら、独自のオブジェクトの作成を担当する単一のクラスが含まれます。このクラスは、このクラスのオブジェクトをインスタンス化せずに、その唯一のオブジェクトに直接アクセスする方法を提供します。

シングルトンパターンの構造

シングルトン モードの主な役割は次のとおりです。

  • シングルトンクラス:インスタンスを一つだけ作成できるクラス
  • アクセスクラス:シングルトンクラスを使用

シングルトンパターンの実装

シングルトン設計パターンには 2 つのタイプがあります。

  • 飢えた中国風: クラスのロードにより単一のインスタンス オブジェクトが作成されます
  • 遅延スタイル: クラスのロードでは単一インスタンス オブジェクトは作成されませんが、オブジェクトが初めて使用されるときに作成されます。

お腹を空かせた中華風

  • はらぺこ中華風 - モード 1 (静的変数モード)

    /**
     * 饿汉式
     * 静态变量创建类的对象
     */
    public class Singleton {
          
          
        // 私有构造方法
        private Singleton() {
          
          }
    
        // 在成员位置直接创建该类的对象
        private static Singleton instance = new Singleton();
    
        // 对外提供静态方法获取该对象
        public static Singleton getInstance() {
          
          
            return instance;
        }
    }
    

    例証します:

    このメソッドは、メンバの位置で Singleton 型の静的変数を宣言し、Singleton クラスのオブジェクト インスタンスを作成します。インスタンス オブジェクトは、クラスがロードされるときに作成されます。オブジェクトが十分に大きい場合、使用されないとメモリの無駄が発生します。

  • 腹ペコ中華風 - 方法2(静的コードブロック法)

    /**
     * 饿汉式
     * 在静态代码块中创建该类对象
     */
    public class Singleton {
          
          
    
        // 私有构造方法
        private Singleton() {
          
          }
    
        // 声明Singleton类型的变量
        private static Singleton instance; // null
    
        // 在静态代码块中进行赋值
        static {
          
          
            instance = new Singleton();
        }
    
        // 对外提供静态方法获取该对象
        public static Singleton getInstance() {
          
          
            return instance;
        }
    }
    

    例証します:

    このメソッドはメンバ位置でシングルトン型の静的変数を宣言し、クラスロード用にも作成される静的コードブロック内にオブジェクトを作成します。したがって、基本的には腹ペコの方法 1 と同じですが、もちろんこの方法にもメモリの浪費の問題があります

怠惰

  • 遅延スタイル - 方法 1 (スレッドは安全ではありません)

    /**
     * 懒汉式
     * 线程不安全
     */
    public class Singleton {
          
          
        // 私有构造方法
        private Singleton() {
          
          }
    
        // 声明Singleton类型的变量
        private static Singleton instance; // null
    
        // 对外提供静态方法获取该对象
        public static Singleton getInstance() {
          
          
            // 判断instance是否为null,如果为null,说明还没有创建Singleton类的对象
            // 如果没有,创建一个并返回,如果有,直接返回
            if (instance == null) {
          
          
                // 多线程环境下:
                // 线程1先抢占到cpu的执行权,但还未执行下面这行代码,此时线程2获取到cpu的执行权,也会进入到该if判断里面,
                // 导致它们都会进行一次创建对象的操作,此时的对象非单例了。
                instance = new Singleton();
            }
            return instance;
        }
    }
    

    例証します:

    上記のコードを見ると、このメソッドはメンバ位置にSingleton型のstatic変数を宣言しており、オブジェクトの代入を行っていないことが分かりますが、いつ代入されるのでしょうか?Singleton クラスのオブジェクトは、getInstance() メソッドが呼び出されて Singleton クラスのオブジェクトを取得するときに作成され、遅延読み込みの効果が得られます。ただし、マルチスレッド環境の場合は、スレッドの安全性の問題が発生します。

  • 遅延スタイル - 方法 2 (スレッドの安全性)

    /**
     * 懒汉式
     * 线程安全
     */
    public class Singleton {
          
          
        // 私有构造方法
        private Singleton() {
          
          }
    
        // 声明Singleton类型的变量
        private static Singleton instance;
    
        // 对外提供静态方法获取该对象 方法上加了synchronized独占锁
        public static synchronized Singleton getInstance() {
          
          
    		// 第一次判断,如果instance的值不为null,不需要抢占锁,直接返回对象
            if (instance == null) {
          
          
                instance = new Singleton();
            }
            return instance;
        }
    }
    

    例証します:

    この方法は遅延読み込みの効果も実現し、同時にスレッドの安全性の問題も解決します。ただし、getInstance()メソッドにキーワードが追加されているためsynchronized、このメソッドの実行効果は特に低くなります上記のコードから、スレッド セーフティの問題はインスタンスの初期化時にのみ発生し、初期化が完了すると問題は存在しないことがわかります。

  • Lazy-way 3 (二重チェックロック)

    遅延モードでのロックの問題についてもう一度説明します。getInstance()メソッドの場合、ほとんどの操作は読み取り操作であり、読み取り操作はスレッドセーフであるため、各スレッドがメソッドを呼び出すためにロックを保持する必要はありません。ロックのタイミングを調整します。これにより、ダブルチェック ロック モードという新しい実装モードも生まれました。

    /**
     * 双重检查方式
     */
    public class Singleton {
          
           
    
        // 私有构造方法
        private Singleton() {
          
          }
    
        // 声明Singleton类型的变量
        private static Singleton instance;
    
       	// 对外提供静态方法获取该对象
        public static Singleton getInstance() {
          
          
    		// 第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
            if (instance == null) {
          
          
                synchronized (Singleton.class) {
          
          
                    // 双重判断,抢到锁之后再次判断是否为null
                    if (instance == null) {
          
          
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    

    ダブルチェック ロック モードは、シングルトン、パフォーマンス、スレッドの安全性の問題を解決する非常に優れたシングルトン実装モードです。上記のダブルチェック ロック モードは完璧に見えますが、実際には問題があります。この問題の理由は、JVM がオブジェクトをインスタンス化するときに最適化と命令の並べ替え操作を実行するためです。

    二重チェック ロック モードによって発生する null ポインタ例外の問題を解決するには、可視性と順序を確保できるvolatileキーワードを使用するだけですvolatile

    /**
     * 双重检查方式
     */
    public class Singleton {
          
          
    
        // 私有构造方法
        private Singleton() {
          
          }
    
        // 声明Singleton类型的变量 使用volatile修饰
        private static volatile Singleton instance;
    
       	// 对外提供静态方法获取该对象
        public static Singleton getInstance() {
          
          
    		// 第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
            if (instance == null) {
          
          
                synchronized (Singleton.class) {
          
          
                    // 抢到锁之后再次判断是否为空
                    if (instance == null) {
          
          
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    

    まとめ:

    キーワードを追加した後のダブルチェック ロック モードvolatileは、より優れたシングルトン実装モードであり、マルチスレッドの場合にパフォーマンスの問題を発生させることなくスレッドの安全性を確保できます。

  • 遅延スタイル - メソッド 4 (静的内部クラス メソッド)

    静的内部クラス シングルトン モードでは、インスタンスは内部クラスによって作成されます。JVM は外部クラスのロード中に静的内部クラスをロードしないため、内部クラスのプロパティ/メソッドが呼び出された場合にのみ実行されます。静的プロパティ。静的プロパティは、static変更されるためインスタンス化されるのは 1 回だけであることが保証され、インスタンス化の順序は厳密に保証されます。

    /**
     * 静态内部类方式
     */
    public class Singleton {
          
          
    
        // 私有构造方法
        private Singleton() {
          
          }
    
        // 定义一个静态内部类
        private static class SingletonHolder {
          
          
            // 在内部类中声明并初始化外部类的对象
            private static final Singleton INSTANCE = new Singleton();
        }
    
        // 对外提供静态方法获取该对象
        public static Singleton getInstance() {
          
          
            return SingletonHolder.INSTANCE;
        }
    }
    

    例証します:

    Singleton クラスが初めてロードされるとき、INSTANCE は初期化されず、初めて getInstance が呼び出されるときのみ、仮想マシンは SingletonHolder をロードして INSTANCE を初期化します。これにより、スレッドの安全性が確保されるだけでなく、インスタンスの一意性も確保されます。シングルトンクラス。

    まとめ:

    静的内部クラスのシングルトン パターンは優れたシングルトン パターンであり、オープン ソース プロジェクトで一般的に使用されるシングルトン パターンです。ロックを追加しなくても、マルチスレッドでのセキュリティが保証され、パフォーマンスへの影響やスペースの無駄がありません。

  • 列挙メソッド

    シングルトン モードを実装する列挙型クラスは、列挙型がスレッド セーフであり、一度だけロードされるため、強く推奨されるシングルトン実装モードです。設計者は、列挙型のこの機能を最大限に活用してシングルトン モードを実装します。記述方法は非常に簡単で、列挙型は使用するシングルトン実装で破壊できない唯一のシングルトン実装モードです。

    /**
     * 枚举方式
     */
    public enum Singleton {
          
          
        INSTANCE;
    }
    

    例証します:

    この列挙方法は、腹を空かした中国の方法に属します。

シングルトンパターンの問題

問題のデモ

シングルトン パターンを破棄します。

上記で定義したシングルトンクラス(Singleton)は、列挙メソッドを除いて複数のオブジェクトを作成できるようにします。シリアル化とリフレクションの 2 つの方法があります。

  • シリアル化 逆シリアル化

    Singleton类

    /**
     * 静态内部类方式
     * 实现序列化接口
     */
    public class Singleton implements Serializable {
          
          
    
        // 私有构造方法
        private Singleton() {
          
          }
    
        private static class SingletonHolder {
          
          
            private static final Singleton INSTANCE = new Singleton();
        }
    
        // 对外提供静态方法获取该对象
        public static Singleton getInstance() {
          
          
            return SingletonHolder.INSTANCE;
        }
    }
    

    Test类

    /**
     * 测试使用序列化和反序列化破坏单例模式
     */
    public class Test {
          
          
        public static void main(String[] args) throws Exception {
          
          
            // 往文件中写对象
            //writeObject2File();
            
            // 从文件中读取对象
            Singleton s1 = readObjectFromFile();
            Singleton s2 = readObjectFromFile();
    
            // 判断两个反序列化后的对象是否是同一个对象
            System.out.println(s1 == s2);
        }
    
        // 从文件读取数据(对象)
        private static Singleton readObjectFromFile() throws Exception {
          
          
            // 1.创建对象输入流对象
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Think\\Desktop\\a.txt"));
            // 2.读取Singleton对象
            Singleton instance = (Singleton) ois.readObject();
    		// 3.释放资源
            ois.close();
            // 4.返回读取到的对象
            return instance;
        }
    
        // 向文件中写数据(对象)
        public static void writeObject2File() throws Exception {
          
          
            // 1.获取Singleton类的对象
            Singleton instance = Singleton.getInstance();
            // 2.创建对象输出流
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Think\\Desktop\\a.txt"));
            // 3.将instance对象写出到文件中
            oos.writeObject(instance);
            // 4.释放资源
            oos.close();
        }
    }
    

    上記のコードを実行した結果は、falseシリアル化と逆シリアル化がシングルトン設計パターンを壊していることを示しています。

  • 反射

    Singleton类

    /**
     * 双重检查方式
     */
    public class Singleton {
          
          
    
        // 私有构造方法
        private Singleton() {
          
          }
        
        private static volatile Singleton instance;
    
        // 对外提供静态方法获取该对象
        public static Singleton getInstance() {
          
          
    
            if (instance != null) {
          
          
                return instance;
            }
    
            synchronized (Singleton.class) {
          
          
                if (instance != null) {
          
          
                    return instance;
                }
                instance = new Singleton();
                return instance;
            }
        }
    }
    

    Test类

    /**
     * 测试使用反射破坏单例模式
     */
    public class Test {
          
          
        public static void main(String[] args) throws Exception {
          
          
            // 1.获取Singleton类的字节码对象
            Class clazz = Singleton.class;
            // 2.获取Singleton类的私有无参构造方法对象
            Constructor constructor = clazz.getDeclaredConstructor();
            // 3.取消访问检查
            constructor.setAccessible(true);
    
            // 4.1 创建Singleton类的对象s1
            Singleton s1 = (Singleton) constructor.newInstance();
            // 4.2 创建Singleton类的对象s2
            Singleton s2 = (Singleton) constructor.newInstance();
    
            // 5.判断通过反射创建的两个Singleton对象是否是同一个对象
            System.out.println(s1 == s2);
        }
    }
    

    上記のコードを実行した結果は、falseリフレクションの方法がシングルトン設計パターンを破っていることを示しています。

注:これら 2 つの問題は、列挙メソッドでは発生しません。

問題解決

  • シリアル化と逆シリアル化によるシングルトン モードの破壊に対する解決策

    シングルトン クラスにメソッドを追加しますreadResolve()。このメソッドは、逆シリアル化中にリフレクションによって呼び出されます。このメソッドが定義されている場合は、このメソッドの値が返されます。定義されていない場合は、新しく作成されたオブジェクトが返されます。

    Singleton类

    /**
     * 静态内部类方式
     * 实现序列化接口
     */
    public class Singleton implements Serializable {
          
          
    
        // 私有构造方法
        private Singleton() {
          
          }
    
        private static class SingletonHolder {
          
          
            private static final Singleton INSTANCE = new Singleton();
        }
    
        // 对外提供静态方法获取该对象
        public static Singleton getInstance() {
          
          
            return SingletonHolder.INSTANCE;
        }
        
        /**
         * 下面是为了解决序列化反序列化破解单例模式
         * 当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回
         */
        private Object readResolve() {
          
          
            return SingletonHolder.INSTANCE;
        }
    }
    

    ソースコード分析

    ObjectInputStream类

        public final Object readObject()
            throws IOException, ClassNotFoundException
        {
          
          
            ...
            // if nested read, passHandle contains handle of enclosing object
            int outerHandle = passHandle;
            try {
          
          
                Object obj = readObject0(false);//重点查看readObject0方法
            ...
        }
    
        private Object readObject0(boolean unshared) throws IOException {
          
          
            ...
            try {
          
          
                switch (tc) {
          
          
                    ...
                    case TC_OBJECT:
                        return checkResolve(readOrdinaryObject(unshared));//重点查看readOrdinaryObject方法
                    ...
                }
            } finally {
          
          
                depth--;
                bin.setBlockDataMode(oldMode);
            }    
        }
    
        private Object readOrdinaryObject(boolean unshared) 
            throws IOException
        {
          
          
            ...
            // isInstantiable 返回true,执行 desc.newInstance(),通过反射创建新的单例类,
            obj = desc.isInstantiable() ? desc.newInstance() : null; 
            ...
            // 在Singleton类中添加 readResolve 方法后 desc.hasReadResolveMethod() 方法执行结果为true
            if (obj != null && 
                handles.lookupException(passHandle) == null && 
                desc.hasReadResolveMethod()) 
            {
          
          
                // 通过反射调用 Singleton 类中的 readResolve 方法,将返回值赋值给rep变量
                // 这样多次调用ObjectInputStream类中的readObject方法,继而就会调用我们定义的readResolve方法,所以返回的是同一个对象。
                Object rep = desc.invokeReadResolve(obj);
                ...
            }
            return obj;
        }
    
  • リフレクションによるシングルトンのクラッキングの解決策

    public class Singleton {
          
          
    
        // 私有构造方法
        private Singleton() {
          
          
            /*
             * 反射破解单例模式需要添加的代码
             */
            if (instance != null) {
          
          
                throw new RuntimeException("不能创建多个对象");
            }
        }
        
        private static volatile Singleton instance;
    
        // 对外提供静态方法获取该对象
        public static Singleton getInstance() {
          
          
    
            if (instance != null) {
          
          
                return instance;
            }
    
            synchronized (Singleton.class) {
          
          
                if (instance != null) {
          
          
                    return instance;
                }
                instance = new Singleton();
                return instance;
            }
        }
    }
    

    例証します:

    この方法の方が理解しやすいです。リフレクション メソッドを呼び出してコンストラクターを作成すると、例外が直接スローされます。この操作は実行しないでください。

JDKソースコード解析 - ランタイムクラス

Runtime クラスは、使用されるシングルトン設計パターンです。

  • ソース コードを通じてどのシングルトン パターンが使用されているかを確認する

    public class Runtime {
          
          
        // 饿汉式初始化
        private static Runtime currentRuntime = new Runtime();
    
        /**
         * Returns the runtime object associated with the current Java application.
         * Most of the methods of class <code>Runtime</code> are instance
         * methods and must be invoked with respect to the current runtime object.
         *
         * @return  the <code>Runtime</code> object associated with the current
         *          Java application.
         */
        public static Runtime getRuntime() {
          
          
            return currentRuntime;
        }
    
        /** Don't let anyone else instantiate this class */
        private Runtime() {
          
          }
        ...
    }
    

    上記のソース コードから、Runtime クラスがHungry Chinese (静的属性) メソッドを使用してシングルトン モードを実装していることがわかります。

  • Runtime クラスのメソッドの使用

    public class RuntimeDemo {
          
          
        public static void main(String[] args) throws IOException {
          
          
            // 获取Runtime类对象
            Runtime runtime = Runtime.getRuntime();
    
    		// 调用runtime的方法
            // 返回 Java 虚拟机中的内存总量
            //System.out.println(runtime.totalMemory());
            // 返回 Java 虚拟机试图使用的最大内存量
            //System.out.println(runtime.maxMemory());
    
            // exec方法,创建一个新的进程执行指定的字符串命令,返回进程对象
            Process process = runtime.exec("ipconfig");
            // 获取命令执行后的结果,通过输入流获取
            InputStream inputStream = process.getInputStream();
            byte[] arr = new byte[1024 * 1024* 100];
            // 读取数组 存到arr字节数组中
            int b = inputStream.read(arr); // 返回读到的字节的个数
            // 将字节数组转换为字符串输出到控制台
            System.out.println(new String(arr, 0, b, "gbk"));
        }
    }
    

    コンソール出力:

    画像-20230102203500745
    これは、ローカルのコマンド ライン ウィンドウでコマンドを実行した場合の効果と同じです。

おすすめ

転載: blog.csdn.net/weixin_53407527/article/details/128620123
おすすめ