美文网首页
单例模式

单例模式

作者: 闽越布衣 | 来源:发表于2019-07-31 08:17 被阅读0次

    描述

        作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。

    简介

    单例模式类图

        单例模式是设计模式中最简单的形式之一。这一模式的目的是使得类的一个对象成为系统中的唯一实例。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

    特点

    • 单例类只能有一个实例。
    • 单例类必须自己创建自己的唯一实例。
    • 单例类必须给所有其他对象提供这一实例。

    优缺点

    优点

    • 实例控制
      单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
    • 灵活性
      因为类控制了实例化过程,所以类可以灵活更改实例化过程。
    • 减少开销
      在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例

    缺点

    • 虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。
    • 使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。
    • 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

    使用场景

    • 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等
    • WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来
    • 需要生成唯一序列的环境

    实现方式

    • 饿汉式
    • 懒汉式
    • 双重检锁
    • 静态内部类
    • 枚举

    示例

    • 饿汉式(静态常量)
     public class Singleton {
        private static Singleton instance = new Singleton();
    
        /**
         * 私有化构造函数
         */
        private Singleton(){}
    
        /**
         * 静态工厂方法
         * @return
         */
        public static Singleton getInstance(){
            return instance;
        }
    }
    

    优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。没有加锁,执行效率会提高。

    缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

    • 饿汉式(静态代码块)
    public class Singleton {
      
        private static Singleton instance;
    
        static {
            instance = new Singleton();
        }
    
        /**
         * 私有化构造函数
         */
        private Singleton() {
        }
    
        /**
         * 静态工厂方法
         *
         * @return
         */
        public static Singleton getInstance() {
            return instance;
        }
    }
    

        这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。

    • 懒汉式(同步方法)
    public class Singleton {
        private static Singleton instance = null;
    
        /**
         * 私有化构造函数
         */
        private Singleton() {
        }
    
        /**
         * 静态工厂方法
         *
         * @return
         */
        public static synchronized Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }
    

    优点:第一次调用才初始化,避免内存浪费。

    缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。

    • 双重检锁【推荐用】
    public class Singleton {
    
        private volatile static Singleton instance = null;
    
    
        /**
         * 私有化构造函数
         */
        private Singleton() {
        }
    
        /**
         * 静态工厂方法
         *
         * @return
         */
        public static Singleton getInstance() {
            //先检查实例是否存在,如果不存在才进入下面的同步块
            if (instance == null) {
                //同步块,线程安全的创建实例
                synchronized (Singleton.class) {
                    //再次检查实例是否存在,如果不存在才真正的创建实例
                    if (instance == null) {
                        instance = new Singleton();
                    }
                }
    
            }
            return instance;
        }
    }
    

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

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

    提示:由于volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化,所以运行效率并不是很高。因此一般建议,没有特别的需要,不要使用。也就是说,虽然可以使用“双重检锁”机制来实现线程安全的单例,但并不建议大量采用,可以根据情况来选用。

    • 静态内部类【推荐用】
    public class Singleton {
        /**
         * 私有化构造函数
         */
        private Singleton() {
        }
    
        /**
         *    类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
         *    没有绑定关系,而且只有被调用到时才会装载,从而实现了延迟加载。
         */
        private static class SingletonInstance {
            /**
             * 静态初始化器,由JVM来保证线程安全
             */
            private static final Singleton INSTANCE = new Singleton();
        }
    
        /**
         * 静态工厂方法
         *
         * @return
         */
        public static Singleton getInstance() {
            return SingletonInstance.INSTANCE;
        }
    }
    

        这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。

        类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。

    优点:避免了线程不安全,延迟加载,效率高。

    • 枚举【推荐用】
    public enum Singleton {
        /**
         * 定义一个枚举的元素,它就代表了Singleton的一个实例。
         */
        INSTANCE;
    
        /**
         * 单例可以有自己的操作
         */
        public void singletonOperation() {
            //功能处理
        }
    }
    

        使用枚举来实现单实例控制会更加简洁,而且无偿地提供了序列化机制,并由JVM从根本上提供保障,绝对防止多次实例化,是更简洁、高效、安全的实现单例的方式。

    相关文章

      网友评论

          本文标题:单例模式

          本文链接:https://www.haomeiwen.com/subject/obgbzftx.html