面试官所认为的单例模式

单例模式是23种GOF模式中最简单,也是最常常出现的一种设计模式,也是面试官最常爱考的一种模式,为何呢?
由于单例模式足够简单,编写一个单例模式代码几分钟就能搞定,因此设计模式中面试官一般会选取单例模式做为出题。
下面把单例模式分几个点,分别说说哪些地方面试官能考你?java

单例模式的意义

一般面试官会很笼统的问你,什么是单例模式?单例模式用来解决了什么痛点?没有单例模式咱们会怎么办?单例模式他有什么缺点吗?

单例模式是最简单的设计模式之一,属于建立型模式,它提供了一种建立对象的方式,确保只有单个对象被建立。这个设计模式主要目的是想在整个系统中只能出现类的一个实例,即一个类只有一个对象。
单例模式的解决的痛点就是节约资源,节省时间从两个方面看:面试

1.因为频繁使用的对象,能够省略建立对象所花费的时间,这对于那些重量级的对象而言,是很重要的.设计模式

2.由于不须要频繁建立对象,咱们的GC压力也减轻了,而在GC中会有STW(stop the world),从这一方面也节约了GC的时间
单例模式的缺点:简单的单例模式设计开发都比较简单,可是复杂的单例模式须要考虑线程安全等并发问题,引入了部分复杂度。安全

扩展:从你的回答中能进行哪些扩展呢?咱们谈到了GC,有可能这时候就会问你GC,STW等知识。谈缺点的时候谈到了复杂的单例模式,
这个时候可能会问你让你设计一个优秀的单例模式你会怎么设计,会怎么实现?多线程

单例模式的设计

一般这里面试官会问你单例模式怎么设计,须要看重哪些方面?通常来讲单例模式有哪些实现方式?

设计单例模式的时候通常须要考虑几种因素:并发

-线程安全
-延迟加载
-代码安全:如防止序列化攻击,防止反射攻击(防止反射进行私有方法调用)
-性能因素函数

通常来讲,咱们去网上百度去搜大概有7,8种实现,,下面列举一下须要重点知道的
饿汉,懒汉(线程安全,线程非安全),双重检查(DCL)(重点),内部类,以及枚举(重点),性能

扩展:咱们上面说到了各个模式的实现,这个时候颇有可能会叫你手写各个模式的代码。固然也有可能会问你线程安全,代码安全等知识。优化

饿汉模式

饿汉模式的代码以下:spa

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

饿汉模式代码比较简单,对象在类中被定义为private static,经过getInstance(),经过java的classLoader机制保证了单例对象惟一。
扩展:

有可能会问instance何时被初始化?

Singleton类被加载的时候就会被初始化,java虚拟机规范虽然没有强制性约束在何时开始类加载过程,可是对于类的初始化,虚拟机规范则严格规定了有且只有四种状况必须当即对类进行初始化,遇到new、getStatic、putStatic或invokeStatic这4条字节码指令时,若是类没有进行过初始化,则须要先触发其初始化。 生成这4条指令最多见的java代码场景是:1)使用new关键字实例化对象2)读取一个类的静态字段(被final修饰、已在编译期把结果放在常量池的静态字段除外)3)设置一个类的静态字段(被final修饰、已在编译期把结果放在常量池的静态字段除外)4)调用一个类的静态方法

class的生命周期?

class的生命周期通常来讲会经历加载、链接、初始化、使用、和卸载五个阶段

class的加载机制

这里能够聊下classloader的双亲委派模型。

双重检查DCL

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
}

synchronized同步块里面可以保证只建立一个对象。可是经过在synchronized的外面增长一层判断,就能够在对象一经建立之后,再也不进入synchronized同步块。这种方案不只减少了锁的粒度,保证了线程安全,性能方面也获得了大幅提高。

同时这里要注意必定要说volatile,这个很关键,volatile通常用于多线程的可见性,可是这里是用来防止指令重排序的。

扩展:

为何须要volatile?volatile有什么用?

  • 首先要回答可见性,这个是毋庸质疑的,而后可能又会考到java内存模型。
  • 防止指令重排序: 防止new Singleton时指令重排序致使其余线程获取到未初始化完的对象。instance = new Singleton()这句,这并不是是一个原子操做,事实上在 JVM 中这句话大概作了下面 3 件事情。1.给 instance 分配内存2.调用 Singleton 的构造函数来初始化成员变量3.将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)

可是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序多是 1-2-3 也多是 1-3-2。若是是后者,则在 3 执行完毕、2 未执行以前,被线程二抢占了,这时 instance 已是非 null 了(但却没有初始化),因此线程二会直接返回 instance,而后使用,而后报错。

  • 顺便也能够说下volatie原理用内存屏障

讲讲synchronized和volatile的区别

这里能够从synchroized能保证原子性,volatile不能保证提及,以及讲下synchroized是重量级锁,甚至能够因此下他和Lock的区别等等。

线程安全通常怎么实现的?

  • 互斥同步。如lock,synchroized
  • 非阻塞同步。如cas。
  • 不一样步。如threadLocal,局部变量。

枚举类

public enum Singleton{
    INSTANCE;
}

默认枚举实例的建立是线程安全的,因此不须要担忧线程安全的问题。同时他也是《Effective Java》中推荐的模式。最后经过枚举类,他能自动避免序列化/反序列化攻击,以及反射攻击(枚举类不能经过反射生成)。

总结

单例模式虽然看起来简单,可是设计的Java基础知识很是多,如static修饰符、synchronized修饰符、volatile修饰符、enum等。这里的每个知识点均可以变成面试官下手的考点,而单例只是做为一个引子,考到最后看你到底掌握了多少。看你的广度和深度究竟是怎么样的。

若是这篇文章对你有帮助,想要了解更多。或者想要1对1的交流。请关注下方公众号吧。

相关文章
相关标签/搜索