共计 2915 个字符,预计需要花费 8 分钟才能阅读完成。
单例模式是 23 种 GOF 模式中最简略,也是最经常出现的一种设计模式,也是面试官最常爱考的一种模式,为什么呢?因为单例模式足够简略,编写一个单例模式代码几分钟就能搞定,所以设计模式中面试官通常会选取单例模式作为出题。上面把单例模式分几个点,别离说说哪些地方面试官能考你?
单例模式的意义
通常面试官会很抽象的问你,什么是单例模式?单例模式用来解决了什么痛点?没有单例模式咱们会怎么办?单例模式他有什么毛病吗?
单例模式是最简略的设计模式之一,属于创立型模式,它提供了一种创建对象的形式,确保只有单个对象被创立。这个设计模式次要目标是想在整个零碎中只能呈现类的一个实例,即一个类只有一个对象。单例模式的解决的痛点就是节约资源,节省时间从两个方面看:
1. 因为频繁应用的对象,能够省略创建对象所破费的工夫,这对于那些重量级的对象而言,是很重要的.
2. 因为不须要频繁创建对象,咱们的 GC 压力也加重了,而在 GC 中会有 STW(stop the world),从这一方面也节约了 GC 的工夫 单例模式的毛病:简略的单例模式设计开发都比较简单,然而简单的单例模式须要思考线程平安等并发问题,引入了局部复杂度。
扩大: 从你的答复中能进行哪些扩大呢?咱们谈到了 GC,有可能这时候就会问你 GC,STW 等常识。谈毛病的时候谈到了简单的单例模式,这个时候可能会问你让你设计一个优良的单例模式你会怎么设计,会怎么实现?
单例模式的设计
通常这里面试官会问你单例模式怎么设计,须要看重哪些方面?一般来说单例模式有哪些实现形式?
设计单例模式的时候个别须要思考几种因素:
- 线程平安 - 提早加载 - 代码平安: 如避免序列化攻打,避免反射攻打(避免反射进行公有办法调用) - 性能因素
一般来说,咱们去网上百度去搜大略有 7,8 种实现,,上面列举一下须要重点晓得的 饿汉,懒汉 (线程平安,线程非平安),双重查看(DCL)(重点), 外部类,以及枚举(重点),上面比对下各个实现:
扩大: 咱们下面说到了各个模式的实现,这个时候很有可能会叫你手写各个模式的代码。当然也有可能会问你线程平安,代码平安等常识。
饿汉模式
饿汉模式的代码如下:
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 个别用于多线程的可见性,然而这里是用来避免指令重排序的。
扩大:
为了不便大家学习交换,建了个 qqjava 后端交换群:3907814, 外面有我珍藏的学习视频(涵盖面试,架构等等),也有很多面试材料,根底学习材料,问题交换探讨,能够退出进来一起交换。
为什么须要 volatile?volatile 有什么用?
1,首先要答复可见性,这个是毋庸质疑的,而后可能又会考到 java 内存模型。
2,避免指令重排序: 避免 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,而后应用,而后报错。
3,顺便也能够说下 volatie 原理用内存屏障
讲讲 synchronized 和 volatile 的区别
这里能够从 synchroized 能保障原子性,volatile 不能保障说起,以及讲下 synchroized 是重量级锁,甚至能够所以下他和 Lock 的区别等等。
线程平安个别怎么实现的?
1,互斥同步。如 lock,synchroized
2,非阻塞同步。如 cas。
3,不同步。如 threadLocal, 局部变量。
枚举类
public enum Singleton{INSTANCE;}
默认枚举实例的创立是线程平安的,所以不须要放心线程平安的问题。同时他也是《Effective Java》中举荐的模式。最初通过枚举类,他能主动防止序列化 / 反序列化攻打,以及反射攻打(枚举类不能通过反射生成)。
总结
单例模式尽管看起来简略,然而设计的 Java 基础知识十分多,如 static 修饰符、synchronized 修饰符、volatile 修饰符、enum 等。这里的每一个知识点都能够变成面试官下手的考点,而单例只是作为一个引子,考到最初看你到底把握了多少。看你的广度和深度到底是怎么样的。