zoukankan      html  css  js  c++  java
  • 创建型设计模式--单例模式

    一、单例模式

    1、什么是单例模式

      采取一定的方法,使程序中的某个类只存在一个实例对象,且该类对外提供一个获取该对象的方法(一般为静态方法)。

    2、单例模式分类

    (1)饿汉式(2种写法,线程安全)
      静态变量
      静态代码块

    (2)懒汉式(3种写法)
      线程不安全
      线程安全,同步方法
      线程安全,同步代码块(不推荐使用)

    (3)双重检查(推荐使用)
    (4)静态内部类(推荐使用)
    (5)枚举(推荐使用)

    3、饿汉式单例模式(静态常量版)

    (1)步骤:
      step1:构造器私有化(防止通过new创建实例对象)
      step2:在类的内部创建实例对象。
      step3:向外暴露一个静态的公共方法用于获取实例对象。
    (2)代码实现:

    package singleton.pattern.demo1;
    
    /**
     * 演示 饿汉式单例模式,静态变量版
     *
     */
    public class Demo {
        public static void main(String[] args) {
            Singleton singleton1 = Singleton.getSingleTon();
            Singleton singleton2 = Singleton.getSingleTon();
            System.out.println(singleton1 == singleton2); // 由于获取的为同一个对象,所以输出为true
        }
    }
    
    /**
     * 饿汉式单例模式,静态变量版
     */
    class Singleton {
        // 在类的内部创建实例对象。使用静态变量,只被加载一次。
        private static Singleton singleton = new Singleton();
    
        /**
         * 构造器私有化(防止通过new创建实例对象)
         */
        private Singleton() {
        }
    
        /**
         * 向外暴露一个静态的公共方法用于获取实例对象。
         *
         * @return 实例对象
         */
        public static Singleton getSingleTon() {
            return singleton;
        }
    }

    (3)优缺点:
      优点:写法简单,在类装载时完成了实例化,避免线程同步问题。
      缺点:在类装载前完成实例化,没有实现懒加载(Lazy Loading),可能造成内存的浪费(比如从不使用该类时会造成内存的浪费)。

    (4)UML图:

    4、饿汉式单例模式(静态代码块)

    (1)步骤:
      step1:构造器私有化(防止通过new创建实例对象)
      step2:在类的内部声明实例对象,并在静态代码块中实例化对象。
      step3:向外暴露一个静态的公共方法用于获取实例对象。
    (2)代码实现:

    package singleton.pattern.demo2;
    
    /**
     * 演示 饿汉式单例模式,静态代码块版
     *
     */
    public class Demo {
        public static void main(String[] args) {
            Singleton singleton1 = Singleton.getSingleTon();
            Singleton singleton2 = Singleton.getSingleTon();
            System.out.println(singleton1 == singleton2); // 由于获取的为同一个对象,所以输出为true
        }
    }
    
    /**
     * 饿汉式单例模式,静态代码块版
     */
    class Singleton {
        // 在类的内部声明一个实例对象
        private static Singleton singleton;
    
        static {
            // 在代码块中实例化一个对象,同样只加载一次
            singleton = new Singleton();
        }
    
        /**
         * 构造器私有化(防止通过new创建实例对象)
         */
        private Singleton() {
        }
    
        /**
         * 向外暴露一个静态的公共方法用于获取实例对象。
         *
         * @return 实例对象
         */
        public static Singleton getSingleTon() {
            return singleton;
        }
    }

    (3)优缺点同上例 饿汉式单例模式(静态常量版)

    5、懒汉式单例模式(线程不安全)

    (1)步骤:
      step1:构造器私有化(防止通过new创建实例对象)
      step2:在类的内部声明实例对象。
      step3:向外暴露一个静态的公共方法用于获取实例对象,在调用该方法时,才去创建实例对象。
    (2)代码实现:

    package singleton.pattern.demo3;
    
    /**
     * 演示 懒汉式单例模式,线程不安全版
     *
     */
    public class Demo {
        public static void main(String[] args) {
            Singleton singleton1 = Singleton.getSingleTon();
            Singleton singleton2 = Singleton.getSingleTon();
            System.out.println(singleton1 == singleton2); // 由于获取的为同一个对象,所以输出为true
        }
    }
    
    /**
     * 懒汉式单例模式,线程不安全版
     */
    class Singleton {
        // 在类的内部声明一个实例对象
        private static Singleton singleton;
    
        /**
         * 构造器私有化(防止通过new创建实例对象)
         */
        private Singleton() {
        }
    
        /**
         * 向外暴露一个静态的公共方法用于获取实例对象。 当调用该方法时,才去检查并创建一个实例对象。
         *
         * @return 实例对象
         */
        public static Singleton getSingleTon() {
            if (singleton == null) {
                singleton = new Singleton();
            }
            return singleton;
        }
    }

    (3)优缺点:
      优点:实现了懒加载。
      缺点:只能在单线程下使用,比如线程A与线程B并发执行到 if (singleton == null), 此时便会产生多个实例对象。

    6、懒汉式单例模式(线程安全,同步方法)

    (1)步骤:
      step1:构造器私有化(防止通过new创建实例对象)
      step2:在类的内部声明实例对象。
      step3:向外暴露一个静态的公共方法(给静态方法加个synchronized关键字,解决同步问题)用于获取实例对象,在调用该方法时,才去创建实例对象。
    (2)代码实现:

    package singleton.pattern.demo4;
    
    /**
     * 演示 懒汉式单例模式,线程安全,同步方法版
     *
     */
    public class Demo {
        public static void main(String[] args) {
            Singleton singleton1 = Singleton.getSingleTon();
            Singleton singleton2 = Singleton.getSingleTon();
            System.out.println(singleton1 == singleton2); // 由于获取的为同一个对象,所以输出为true
        }
    }
    
    /**
     * 懒汉式单例模式,线程安全,同步方法版
     */
    class Singleton {
        // 在类的内部声明一个实例对象
        private static Singleton singleton;
    
        /**
         * 构造器私有化(防止通过new创建实例对象)
         */
        private Singleton() {
        }
    
        /**
         * 向外暴露一个静态的公共方法用于获取实例对象,并给方法加个synchronized关键字,解决同步的问题。
         * 当调用该方法时,才去检查并创建一个实例对象。
         *
         * @return 实例对象
         */
        public static synchronized Singleton getSingleTon() {
            if (singleton == null) {
                singleton = new Singleton();
            }
            return singleton;
        }
    }

    (3)优缺点:
      优点:实现了懒加载,解决了线程不安全的问题。
      缺点:效率太低,每个线程想获取实例对象时,均会触发同步方法,此时会等待前一个线程调用结束后才能使用,使效率低。

    7、懒汉式单例模式(同步代码块方法,线程不一定安全)

    (1)步骤:
      step1:构造器私有化(防止通过new创建实例对象)
      step2:在类的内部声明实例对象。
      step3:向外暴露一个静态的公共方法(在静态方法内部定义一个代码块,解决同步问题)用于获取实例对象,在调用该方法时,才去创建实例对象。
    (2)代码实现:

    package singleton.pattern.demo5;
    
    /**
     * 演示 懒汉式单例模式,线程不一定安全,同步代码块版
     *
     */
    public class Demo {
        public static void main(String[] args) {
            Singleton singleton1 = Singleton.getSingleTon();
            Singleton singleton2 = Singleton.getSingleTon();
            System.out.println(singleton1 == singleton2); // 由于获取的为同一个对象,所以输出为true
        }
    }
    
    /**
     * 懒汉式单例模式,线程不一定安全,同步代码块版
     */
    class Singleton {
        // 在类的内部声明一个实例对象
        private static Singleton singleton;
    
        /**
         * 构造器私有化(防止通过new创建实例对象)
         */
        private Singleton() {
        }
    
        /**
         * 向外暴露一个静态的公共方法用于获取实例对象,在方法内部加个同步代码块。 当调用该方法时,才去检查并创建一个实例对象。
         *
         * @return 实例对象
         */
        public static Singleton getSingleTon() {
            if (singleton == null) {
                synchronized (Singleton.class) {
                    singleton = new Singleton();
                }
            }
            return singleton;
        }
    }

    (3)优缺点:
      优点:优化了了上例 懒汉式单例模式(线程安全,同步方法) 的效率问题。
      缺点:能满足大部分线程安全的情况,但是若线程A与线程B并发执行到 if (singleton == null),此时同步代码块的作用就不存在了, 会产生多个实例对象。

    8、双重检查(Double Check)

    (1)步骤:
      step1:构造器私有化(防止通过new创建实例对象)
      step2:在类的内部声明实例对象,并使用volatile关键字(保证可见性,且防止因JVM指令重排使代码执行顺序不对,从而导致代码执行有误)。
      step3:向外暴露一个静态的公共方法(在静态方法内部定义一个代码块,解决同步问题)用于获取实例对象,在调用该方法时,才去创建实例对象。在上例 懒汉式单例模式(同步代码块方法,线程不一定安全) 的基础上,给同步代码块内部加个检查处理。
    (2)代码实现:

    package singleton.pattern.demo6;
    
    /**
     * 演示 单例模式,双重检查版
     *
     */
    public class Demo {
        public static void main(String[] args) {
            Singleton singleton1 = Singleton.getSingleTon();
            Singleton singleton2 = Singleton.getSingleTon();
            System.out.println(singleton1 == singleton2); // 由于获取的为同一个对象,所以输出为true
        }
    }
    
    /**
     * 单例模式,双重检查版
     */
    class Singleton {
        // 在类的内部声明一个实例对象
        private static volatile Singleton singleton;
    
        /**
         * 构造器私有化(防止通过new创建实例对象)
         */
        private Singleton() {
        }
    
        /**
         * 向外暴露一个静态的公共方法用于获取实例对象,在方法内部加个同步代码块,在代码块内部增加一个检查处理。 当调用该方法时,才去检查并创建一个实例对象。
         *
         * @return 实例对象
         */
        public static Singleton getSingleTon() {
            if (singleton == null) {
                synchronized (Singleton.class) {
                    if (singleton == null) {
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        }
    }

    (3)优缺点:
      优点:线程安全,延迟加载,效率高。常用于多线程开发。

    9、静态内部类

    (1)步骤:
      step1:构造器私有化(防止通过new创建实例对象)
      step2:在类的内部定义一个静态内部类(只有被调用时,才会被加载),并在内部类中实例化对象。
      step3:向外暴露一个静态的公共方法,并在方法中调用静态内部类,用于获取实例对象。
    (2)代码实现:

    package singleton.pattern.demo7;
    
    /**
     * 演示 单例模式,静态内部类版
     *
     */
    public class Demo {
        public static void main(String[] args) {
            Singleton singleton1 = Singleton.getSingleTon();
            Singleton singleton2 = Singleton.getSingleTon();
            System.out.println(singleton1 == singleton2); // 由于获取的为同一个对象,所以输出为true
        }
    }
    
    /**
     * 单例模式,静态内部类版
     */
    class Singleton {
        /**
         * 构造器私有化(防止通过new创建实例对象)
         */
        private Singleton() {
        }
    
        /**
         * 静态内部类,在被调用的时候才会被加载,实现懒加载。 且内部使用静态常量实例化一个对象,保证了线程安全问题。
         */
        public static class SingleTonInstance {
            public static final Singleton INSTANCE = new Singleton();
        }
    
        /**
         * 向外暴露一个静态的公共方法用于获取实例对象,在方法内部加个同步代码块,在代码块内部增加一个检查处理。 当调用该方法时,才去检查并创建一个实例对象。
         *
         * @return 实例对象
         */
        public static Singleton getSingleTon() {
            return SingleTonInstance.INSTANCE; // 调用静态内部类的静态属性
        }
    }

    (3)优缺点:
      优点:利用JVM的类加载机制,保证了实例化对象时只有一个线程,从而线程安全。在被调用时静态内部类才会被加载并实例化对象,从而实现懒加载,效率高。

    (4)UML图:

    10、枚举

    (1)步骤:
      step1:定义一个枚举类型。
      step2:调用即可
    (2)代码实现:

    package singleton.pattern;
    
    /**
     * 演示 单例模式,枚举版
     *
     */
    public class Demo {
        public static void main(String[] args) {
            SingleTon singleton1 = SingleTon.INSTACNE;
            SingleTon singleton2 = SingleTon.INSTACNE;
            System.out.println(singleton1 == singleton2); // 由于获取的为同一个对象,所以输出为true
        }
    }
    
    /**
     * 单例模式,枚举版
     */
    enum SingleTon {
        INSTACNE;
        public void show() {
            System.out.println("hello world");
        }
    }

    (3)优缺点:
      优点:使用枚举类型创建(enum本质是个继承java.lang.Enum类的final class),保证线程安全,且可以防止反序列化重新创建新的对象。

    11、JDK中的单例模式举例(Runtime)

    (1)部分源码

    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() {}
    }

    (2)可以看到上述代码中采用的是 饿汉式单例模式(静态变量版)。

    12、单例模式使用注意

    (1)当频繁创建、销毁某个对象时,可以采用单例模式。
    (2)使用单例模式时,需调用相关方法获取实例,而不是通过new。
    (3)当创建对象消耗资源过多时,但又经常使用时,可以采用单例模式创建。

  • 相关阅读:
    Centos7新特性——systemd取代init管理服务
    Git初探
    Nginx内置变量
    Nginx初探
    PHP多进程初步
    golang消息队列nsq
    golang 的 go异步编程通道要注意的问题
    golang 连接池mysql
    golang centos运行方法
    golang go path和go mod的区别
  • 原文地址:https://www.cnblogs.com/huoyz/p/14398553.html
Copyright © 2011-2022 走看看