• 【java设计模式】-04单例模式


    单例模式

    定义: 确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

    类型: 创建类模式

    类图:

    image

    单例模式特点

    1、单例类只能有一个实例。

    2、单例类必须自己创建自己的唯一实例。

    3、单例类必须给所有其他对象提供这一实例。

    单例模式应该是23种设计模式中最简单的一种模式了。它有以下几个要素:

    • 私有的构造方法
    • 指向自己实例的私有静态引用
    • 以自己实例为返回值的静态的公有的方法

    单例模式的实现方式

    单例模式根据实例化对象时机的不同分为两种:

    一种是饿汉式单例,一种是懒汉式单例。

    饿汉式单例在单例类被加载时候,就实例化一个对象交给自己的引用;

    懒汉式在调用取得实例方法的时候才会实例化对象。

    1)饿汉式单例

    是否 Lazy 初始化:否
    是否多线程安全:是
    实现难度:易
    描述:这种方式比较常用,但容易产生垃圾对象。
    优点:没有加锁,执行效率会提高。
    缺点:类加载时就初始化,浪费内存。
    /**
     * 饿汉式单例
     * <p>
     * 饿汉式是典型的空间换时间,当类装载的时候就会创建类的实例,
     * 不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断,节省了运行时间。
     *
     * @author kaifeng
     */
    public class HungrySingleton {
        private static HungrySingleton hungrySingleton = new HungrySingleton();
    
        /**
         * 私有构造函数
         */
        private HungrySingleton() {
        }
    
        /**
         * 获取实例对象
         */
        public static HungrySingleton getInstance() {
            return hungrySingleton;
        }
    }

    2)懒汉式单例

    是否 Lazy 初始化:是
    是否多线程安全:是
    实现难度:易
    描述:这种单例是典型的时间换空间,只有使用的时候才会实例化,但是每次获取实例的时候都会进行判断,是否已经实例,花费了一些判断时间,如果一直没有使用就不会实例化,节省了内存空间
    优点:第一次调用才初始化,避免内存浪费。
    缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
    /**
     * 懒汉式单例
     * <p>
     * 这种单例是典型的时间换空间,只有使用的时候才会实例化,但是每次获取实例的时候都会进行判断,
     * 是否已经实例,花费了一些判断时间,如果一直没有使用就不会实例化,节省了内存空间
     *
     * @author kaifeng
     */
    public class LazySingleton {
        private static LazySingleton instance = null;
    
        /**
         * 私有构造函数
         */
        private LazySingleton() {
        }
    
        /**
         * 静态工厂方法,实例化对象
         */
        public static synchronized LazySingleton getInstance() {
            if (instance == null) {
                instance = new LazySingleton();
            }
            return instance;
        }
    }

    3)双检锁/双重校验锁(DCL,即 double-checked locking)

    是否 Lazy 初始化:是
    是否多线程安全:是
    实现难度:较复杂
    描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
    /**
     * 双检锁/双重校验锁(DCL,即 double-checked locking)
     *
     * @author kaifeng
     */
    public class DCLSingleton {
    
        private volatile static DCLSingleton instance;
    
        private DCLSingleton() {
        }
    
        public static DCLSingleton getInstance() {
            //第一次检查实例是否存在,如果不存在才进入下面的同步块,否则直接返回实例
            if (instance == null) {
                //同步代码块,线程安全的创建实例
                synchronized (DCLSingleton.class) {
                    //第二次检查实例是否存在,如果不存在才真正的创建实例
                    if (instance == null) {
                        instance = new DCLSingleton();
                    }
                }
            }
            return instance;
        }
    }

    所谓“双重检查加锁”机制:并不是每次进入getInstance方法都需要同步,而是先检查实例是否存在,如果不存在才进行下面的同步块,这是第一重检查,进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。

    “双重检查加锁”机制的实现会使用关键字volatile,被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。

    4)Initialization-on-demand holder idiom

    是否 Lazy 初始化:是
    是否多线程安全:是
    实现难度:一般
    描述:这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
    /**
     * 在这个类级内部类里面去创建对象实例,只要不使用到这个类级内部类,
     * 那就不会创建对象实例,从而同时实现延迟加载和线程安全。
     *
     * @author kaifeng
     */
    public class Singleton {
        private Singleton() {
        }
    
        /**
         * 类级的内部类,就是静态的成员式内部类,该内部类的实例与外部类的实例
         * 没有绑定关系,而且只有被调用时才会装载,从而实现了延迟加载。
         */
        private static class SingletonHolder {
            /**
             * 静态初始化器,由JVM来保证线程安全
             */
            private static Singleton instance = new Singleton();
        }
    
        public static Singleton getInstance() {
            return SingletonHolder.instance;
        }
    }

    当getInstance方法第一次被调用的时候,第一次读取SingletonHolder.instance,导致SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。

    这个模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。

    5)枚举

    是否 Lazy 初始化:否
    是否多线程安全:是
    实现难度:易
    描述:这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
    这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
    不能通过 reflection attack 来调用私有构造方法。
    /**
     * @author kaifeng
     */
    public enum EnumSingleton {
        /**
         * 定义一个枚举的元素,它就代表了Singleton的一个实例
         */
        instance;
    
        /**
         * 单例可以有自己的操作
         */
        public void singletonCustom() {
            //自定义操作
        }
    }
    

    单例模式的优点:

    • 在内存中只有一个对象,节省内存空间。
    • 避免频繁的创建销毁对象,可以提高性能。
    • 避免对共享资源的多重占用。
    • 可以全局访问。

    适用场景:

    • 需要频繁实例化然后销毁的对象。
    • 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
    • 有状态的工具类对象。
    • 频繁访问数据库或文件的对象。
    • 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器

    单例模式注意事项:

    • 只能使用单例类提供的方法得到单例对象,不要使用反射,否则将会实例化一个新对象。
    • 不要做断开单例类对象与类中静态引用的危险操作。
    • 多线程使用单例使用共享资源时,注意线程安全问题。
  • 相关阅读:
    python全栈学习--day57(响应式页面-@media介绍,移动端单位介绍,Bootstrap学习)
    网络编程-Socket介绍
    网络编程-五层协议详解
    python-封装
    python- 类的多态与多态性
    python-接口类与抽象类
    python-类的组合和使用
    python-类继承与重用
    python-面向对象编程小结
    python-属性的查找与绑定方法
  • 原文地址:https://www.cnblogs.com/liukaifeng/p/10052612.html
Copyright © 2020-2023  润新知