美文网首页
单例模式的7种写法

单例模式的7种写法

作者: 科威舟VS求利 | 来源:发表于2020-07-12 18:24 被阅读0次

    设计模式(Design pattern)一共有23种,而单例模式(Singleton Pattern)是 其中最简单的设计模式之一,这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。单例模式有很多种写法,下面我们介绍一下常见的7种写法。

    说明

    单例模式有几个注意点:

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

    常见的单例模式主要分为懒汉式、饿汉式,这两种又衍生出了很多种写法。还有一种写法是单例的枚举模式,本文将逐一介绍。

    懒汉模式

    懒汉式,顾名思义就是实例在用到的时候才去创建,“比较懒”,用的时候才去检查有没有实例,如果有则返回,没有则新建。它有线程安全和不安全的两种写法,下面我们由浅入深,逐一讨论。

    1. 单例实例在第一次使用时进行创建

    这是一种比较常规和简单的写法,值得注意的是它的构造方法是私有的,一个线程不安全的写法,多线程环境下,可能运行多次就会new多次,会产生一定的影响。

    @Slf4j
    @NotThreadSafe
    public class SingletonDemo1 {
    
        //私有构造函数,让构造函数为 private,这样该类就不会被实例化
        //如果不将它们设为私有,则将创建多个Singleton类实例,保证了单例类只能有一个实例
        private SingletonDemo1() {
    
        }
    
        //单例对象
        private static SingletonDemo1 instance = null;
    
        //静态工厂方法获取对象
        public static SingletonDemo1 getInstance() {
            //在单线程模式下没有问题,多线程下,可能运行多次就会new多次,会产生一定的影响
            if (instance == null) {
                instance = new SingletonDemo1();
            }
            return instance;
        }
        
    }
    

    2.通过加入 synchronized 关键词实现线程安全的懒汉式

    通过加入 synchronized 关键词可以实现线程安全的,但是加入synchronized之后失去了并发性,同一时刻只允许一个线程访问。优点:线程安全,缺点:当然是缺乏并发性,给性能带来一定的开销。

    @Slf4j
    @ThreadSafe
    @NotRecommend
    public class SingletonDemo3 {
    
        //私有构造函数
        private SingletonDemo3() {
    
        }
    
        //单例对象
        private static SingletonDemo3 instance = null;
    
        //静态工厂方法获取对象
        //加上synchronized关键字
        public static synchronized SingletonDemo3 getInstance() {
            if (instance == null) {
                instance = new SingletonDemo3();
            }
            return instance;
        }
        
    }
    

    3.懒汉模式的双重同步锁代理模式(线程不安全)

    懒汉模式的另一种写法是双重同步锁代理模式,又称双重检测机制 (DCL),如下:

    @Slf4j
    @NotThreadSafe
    public class SingletonDemo4 {
    
        //私有构造函数
        private SingletonDemo4() {
    
        }
        
        //单例对象
        private static SingletonDemo4 instance = null;
    
        //静态工厂方法获取对象
        public static SingletonDemo4 getInstance() {
            if (instance == null) {//双重检测机制 DCL     //B
                synchronized (SingletonDemo4.class) {  //同步锁
                    if (instance == null) {
                        instance = new SingletonDemo4();  //A -3
                    }
                }
            }
            return instance;
        }
        
    }
    

    我们在getInstance()里面又加了一个双重检测校验,这种方式在单线程下面没有问题,而在多线程情况下就会不安全了,一般情况下它的执行顺序为:

    1. memory = allocate() 分配对象的内存空间
    2. ctorInstance()初始化对象
    3. instance = memory 设置instance指向放分配的内存

    由于可能会出现指令重排的情况,由于jvm和cpu优化,发生了指令重排,执行顺序就可能为:1、3、2。
    例如: 两个线程,线程A、B同时调用这个getInstance方法,A执行到了下面A的位置,而B执行到了下面B的位置,如果按照上面说的指令重排的1,3,2顺序 A执行了3,而B线程来了之后,发现A线程已经有值了(但是它还没执行2对其进行初始化操作),然后直接返回了,就会出问题了。此刻,我们需要在当前这种情况下,防止指令重排,就引出了volatile关键字。

    4.懒汉模式的双重同步锁代理模式(线程安全)

    限制它产生指令重排,用volatile关键字,如下:

    @Slf4j
    @ThreadSafe
    public class SingletonDemo5 {
    
        //私有构造函数
        private SingletonDemo5() {
    
        }
    
        /**
         * 1、memory = allocate() 分配对象的内存空间
         * 2、ctorInstance()初始化对象
         * 3、instance = memory 设置instance指向放分配的内存
         */
    
        //单例对象  volatile + 双重检测机制  ->   禁止指令重排
        private static volatile SingletonDemo5 instance = null;
    
        //静态工厂方法获取对象
        public static SingletonDemo5 getInstance() {
            if (instance == null) {//双重检测机制 DCL     //B
                synchronized (SingletonDemo5.class) {  //同步锁
                    if (instance == null) {
                        instance = new SingletonDemo5();  //A -3
                    }
                }
            }
            return instance;
        }
    
    
    }
    

    恶汉模式

    5.常规恶汉模式写法

    恶汉模式它在类装载使用时进行创建(能保证线程安全),同时也会引入一个缺点,就是如果类的构造方法中存在着过多的处理,会导致这个类加载的时候特别慢。

    @Slf4j
    @ThreadSafe
    public class SingletonDemo2 {
    
        //饿汉模式缺点--如果类的构造方法中存在着过多的处理,会导致这个类加载的时候特别慢
    
        //私有构造函数
        private SingletonDemo2() {
    
        }
    
        //单例对象
        private static SingletonDemo2 instance = new SingletonDemo2();
    
        //静态工厂方法获取对象
        public static SingletonDemo2 getInstance() {
            return instance;
        }
    }
    

    6.通过静态代码块方式的汉模式

    通过静态代码块的方式来实例化类对象,这里有点需要注意,就是在写静态域或者静态代码块的时候,我们要注意顺序,如下面1和2顺序不能颠倒,颠倒了之后他们的执行结果会有所不同。

    @Slf4j
    @ThreadSafe
    public class SingletonDemo6 {
    
        //饿汉模式缺点--如果类的构造方法中存在着过多的处理,会导致这个类加载的时候特别慢
    
        //私有构造函数
        private SingletonDemo6() {
    
        }
    
        //单例对象
        //当我们在写静态域或者静态代码块的时候,我们要注意顺序,如下面1和2顺序不能颠倒,
        //他们的执行结果会有所不同
        //1
        private static SingletonDemo6 instance = null;
    
        //2
        static {
            instance = new SingletonDemo6();
        }
    
    
        //静态工厂方法获取对象
        public static SingletonDemo6 getInstance() {
            return instance;
        }
    
        public static void main(String[] args) {
            System.out.println(getInstance().hashCode());
            System.out.println(getInstance().hashCode());
        }
    
    }
    

    枚举模式

    7.枚举方式的单例模式

    说实话,这种方式用的比较少,但是还是比较推荐的一种写法,一来它是线程安全的。它相比于我们的懒汉模式在安全性方面得到了保证,它相比于饿汉模式,在实际调用的时候才做初始化

    @Slf4j
    @ThreadSafe
    @Recommend
    public class SingletonDemo7 {
    
        //私有构造函数
        private SingletonDemo7() {
    
        }
    
        public static SingletonDemo7 getInstance() {
            return Singleton.INSTANCE.getInstance();
        }
    
        private enum Singleton {
            INSTANCE;
    
            private SingletonDemo7 singleton;
    
            //JVM保证这个方法绝对只调用一次
            Singleton() {
                singleton = new SingletonDemo7();
            }
    
            public SingletonDemo7 getInstance() {
                return singleton;
            }
        }
    }
    

    总结

    本文总结了7种单例模式的具体写法,有浅入深。从最简单的懒汉模式入手,引出了线程安全的实现方式。从DCL的懒汉模式的cpu指令重排,又引入了volitate关键字。此外,还讲了恶汉模式的常规写法,以及通过静态代码块的写法。最后,举了比较推荐的枚举方式的单例模式。

    图文与内容无关系列

    参考:https://github.com/nuptkwz/notes/tree/master/technology/designpattern

    相关文章

      网友评论

          本文标题:单例模式的7种写法

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