美文网首页
(七)单例模式

(七)单例模式

作者: feiry | 来源:发表于2019-08-05 22:30 被阅读0次

    顾名思义,就是只能有一个实例,单例模式的运用场景非常多,有多种创建方法

    饿汉式

    public class Singleton {
        
        private Singleton() {
        }
        
        private static final Singleton singleton = new Singleton();
    
        public static Singleton getInstance() {
            return singleton;
        }
    }
    

    利用类加载机制,再加载类的时候就初始化好了实例,调用方法直接获取即可
    特点:
    1.线程安全
    2.在加载类时初始化

    懒汉式

    其他懒汉式写法不讨论,直接使用正确的懒汉式写法

    class Singleton2 {
    
        private Singleton2() {
        }
    
        private static volatile Singleton2 singleton;
    
        public static Singleton2 getInstance() {
            if (null == singleton) {//1.
                synchronized (Singleton2.class) {
                    if (null == singleton) {//2.
                        singleton = new Singleton2();
                    }
                }
            }
            return singleton;
        }
    }
    

    使用双检索+volatile 来保证线程安全、效率和懒加载问题

    • 第一个判空是保证效率问题,如果成员变量非空直接返回
    • synchronized保证线程安全,如果多个线程进入那么只有一个线程可以进入到new环节
    • 如果两个线程都通过了第一个判空的步骤
      线程一进入同步块,线程二等待,这时变量是null所以会new对象,然后线程一出同步块。
      线程二进入同步块,发现变量不为null了,直接出同步块
      所以第二个判空是保证只实例化一次
    • 为什么要加volatile?
      volatile的作用有哪些?线程可见性,禁用指令重排序,这里就是用到了禁用指令重排序的特性
      new关键字不是一个原子操作,它有三步:1.创建对象(分配内存空间),2.初始化对象,3.返回地址给引用变量
      由于JVM内部的优化机制,指令可能会重新排序
      例如线程A进入同步块,开始new对象,由于指令重排序,可能第三步最先执行,那么成员变量这时候已经有了一个地址值。
      这时线程B进入方法(没有进入同步块),发现第一个判断singleton不是空,然后就直接返回了这个引用变量,此时线程A还未初始化对象,但是线程B已经把变量返回出去了,那么外部使用这个变量就会发生异常,这就为什么要加volatile的原因

    静态内部类

    class Singleton3 {
    
        private Singleton3() {
        }
    
        public static Singleton3 getInstance() {
            return SingletonHolder.singleton;
        }
    
        private static class SingletonHolder{
            private static final Singleton3 singleton = new Singleton3(); 
        }
    }
    

    同样例如类加载器的机制,只有当时用getInstance方法时,才会实例化变量,和饿汉式比,同样没有使用synchronized,但是多一个懒加载的效果

    枚举

    enum  EnumSingleton {
        INSTANCE;
        public EnumSingleton getInstance(){
            return INSTANCE;
        }
    }
    

    大牛推荐的单例模式实现方式,代码简单,并且使用反射和序列化后的对象依然是同一个内存地址,上面的实现方式都不能避免反射攻击,但是没什么人用
    企业中用java开发基本没有不用spring框架的,spring的IOC容器中默认就是单例的,使用spring就行

    相关文章

      网友评论

          本文标题:(七)单例模式

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