• 面试中单例模式引发的思考


    0、前言

    考察:并发?类加载?序列化?

    什么是单例模式:保证一个类只有一个实例,并且提供一个全局可以访问的入口。

    为什么要用单例模式:

    • 节省内存,节省计算。
    • 保证结果的正确

    1、单例模式的应用场景

    • 无状态的工具类:日志工具,字符创工具
    • 全局信息类:全局计数,环境变量

    2、单例模式的5种写法

    【饿汉式】

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

    添加了静态代码块——把类加载这部分逻辑放到静态中

    public class Singleton02 {
    
        private static Singleton02 singleton02;
        static {
            singleton02 = new Singleton02();
        }
        private Singleton02(){}
        public Singleton02 getInstance(){
            return singleton02;
        }
    }

    以上两种都是类初始化的时候就直接加载了。但是如果这个类从始至终都没有使用过的话就会造成内存浪费。

    【懒汉模式】 :判断类不为空的话就初始化一个对象

    public class Singleton03 {
    
        private static Singleton03 singleton03;
    
        private Singleton03(){}
    
        public static Singleton03 getInstance(){
            if (singleton03 == null){
                singleton03 = new Singleton03();
            }
            return singleton03;
        }
    }

    上面那个线程不安全:多线程情况下回创建多个实例

    public class Singleton03 {
    
        private static Singleton03 singleton03;
    
        private Singleton03(){}
    
        public static synchronized Singleton03 getInstance(){
            if (singleton03 == null){
                singleton03 = new Singleton03();
            }
            return singleton03;
        }
    }

    以上代码 多线程情况下效率不高

    public class Singleton04 {
        private static Singleton04 singleton04;
        private Singleton04(){}
        public static Singleton04 getInstance(){
            if (singleton04 == null){
                synchronized (Singleton04.class) {
                    singleton04 = new Singleton04();
                }
            }
            return singleton04;
        }
    
    }

    提高了效率 但是有可能会多实例

    public class Singleton05 {
        
        //线程安全
        private static volatile Singleton05 singleton05;
        private Singleton05(){}
        public static Singleton05 getInstance(){
            if (singleton05 == null){  //去掉后导致串行
                synchronized (Singleton05.class) {
                    if (singleton05 == null) { //去掉后
                        singleton05 = new Singleton05();
                    }
                }
            }
            return singleton05;
        }
    
    }

    双重检查 能够解决上面破坏实例化的问题。

    JVM 在这个方法中做了几件事情:顺序可能是1-2-3   或者  1-3-2

    1、给singleton分配内存空间

    2、调用singleton的构造函数等来初始化singleton

    3、将singleton对象指向分配的内存空间(执行完这一步的话singleton就不是null了)

    volatile关键字要注意下哦!能够防止上面说的重排序而导致的报错

    public class Singleton06 {
        
        //静态内部类的写法
        private Singleton06(){}
    
        private static class SingletonInstance{
            private static final Singleton06 singleton06 = new Singleton06();
        }
    
        public static Singleton06 getInstance(){
            return SingletonInstance.singleton06;
        }
    }
    public enum  Singleton07 {
        INSTANCE;
        public void whateverMethod(){}
    }

    枚举主要是为了防止反序列化 和 反射来导致多个实例被构造出来。

    参考https://www.cnblogs.com/cjn123/p/12159536.html

    3、枚举类写法的优点

    《Effective Java》 用私有构造器或者枚举类型强化SingleTon(单例)属性

    单例(singleton)就是一个只实例化一次的类。使类成为单例可能会使它的测试变得困难,因为除非它实现了作为其类型的接口,否则不可能用模拟实现来代替这个单例。下面是几种实现单例的方法:

    1、共有静态成员是final类型

    public class SingletonEffective01 {
        public static final SingletonEffective01 INSTANCE = new SingletonEffective01();
        private SingletonEffective01(){}
        public void lTB(){}
    }

    私有构造器之后执行一次,实例化Elvis.INSTANCE属性,由于缺少公有(public)的或者受保护(protected)的构造器,所以Elvis一旦被实例化,就只会存在一个Elvis的实例(注:反射是可以实现多次调用私有的构造器,若需要抵御这种攻击,则可以修改私有构造器,让它在被创建第二个实例时,抛出异常,或直接返回第一个实例对象)。

    2、公有的成员是一个静态方法。

    public class SingletonEffective02 {
    
        public static final SingletonEffective02 INSTANCE = new SingletonEffective02();
        private SingletonEffective02(){}
        public static SingletonEffective02 getInstance(){return INSTANCE;} //新增了这个方法
        public void lTB(){}
    
        //为防止反序列化时又创建了一个新的实例,需要在反序列化是直接返回新的数据
    
        //1、防止反序列化获取多个对象的漏洞。
        //2、无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。
        //3、实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象
        private Object readResolve(){
            return INSTANCE;
        }
    }

    这个方式可以通过公有静态方法将类改为非单例,但用户代码不需要改变。

    为了使利用这其中一种方法实现的SingleTon类变成是可序列化的(Serializable),仅仅在申明上加上“implements Serializable”是不够的。为了维护并保证SingleTon,必须申明所有实例都是瞬时的(transient),并提供一个readResolve方法,否者,每次反序列化一个序列化的实例时,都会创建一个新的实例,比如说,在我们的实例中,会导致“假冒的Elvis”。为了防止这种情况,要在Elvis类中加入readResolve方法

    3、使用枚举实现单例

    public class SingletonEffective03{
        private SingletonEffective03(){}
        public static SingletonEffective03 getInstance(){
            return Singleton.INSTANCE.getInstance();
        }
    
        private enum Singleton{
            INSTANCE;
    
            private SingletonEffective03 singleton;
            //JVM会保证此方法绝对只调用一次
            Singleton(){
                singleton = new SingletonEffective03();
            }
            public SingletonEffective03 getInstance(){
                return singleton;
            }
        }
    }

    4、引发的一些小知识点

    final关键字:

    volatile关键字:

  • 相关阅读:
    DataSet调用Dispose必须吗
    Python基础
    windows下pip升级到8.1.1 及安装Selenium
    python文件编码说明 coding
    Response.End() VS Context.ApplicationInstance.CompleteRequest()
    Python练习正则
    Python2.7爬虫练习爬百度百科python词条
    未知错误:1000正在终止线程
    debug pin用
    读取文件
  • 原文地址:https://www.cnblogs.com/cjn123/p/12148351.html
Copyright © 2020-2023  润新知