1、面试官发问,话题牵引
思路:
无论让你讲什么设计模式,统一引到单例设计模式。
套路:
设计模式要将就讲最难的和应用最广的。
单例模式最难!
单例模式简单?
考虑了序列化吗?考虑了反射吗?考虑了安全性吗?考虑了多线程和高并发吗?
2、单例模式概况
写法很多,用得比较多的主要就三种懒汉模式,饿汉模式,枚举单例
主要实现思路:
唯一空构造器私有化,静态方法返回唯一实例
3、懒汉单例
1、为了方便讲解,我先从懒汉单例讲起。
拿自己简历背面开始写懒汉单例代码。
public class Singleton {
private static Singleton stu=null;
private Singleton() {
}
public static Singleton getInstance() {
if(stu==null){
return new Singleton();
}
return stu;
}
}
简单讲讲方法为何static,对象声明为何static。
4、连环发问1:多线程如何实例唯一?
解决:
将静态方法改成静态同步方法
public class Singleton {
private static Singleton stu=null;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if(stu==null){
return new Singleton();
}
return stu;
}
}
5、连环发问2:性能上好吗?
解决:
同步方法改同步代码块
public class Singleton {
private static Singleton stu=null;
private Singleton() {
}
public static Singleton getInstance() {
synchronized (""){
if(stu==null){
return new Singleton();
}
return stu;
}
}
}
讲锁监视器
6、连环发问3:性能上还能优化吗?
解决:
双层锁校验
public class Singleton {
private static Singleton stu=null;
private Singleton() {
}
public static Singleton getInstance() {
if(stu==null){
synchronized (""){
if(stu==null){
return new Singleton();
}
}
}
return stu;
}
}
7、连环发问4:没有任何问题了吗?
分析:
jvm处于性能原因会进行指令重排,高并发以上代码可能造成对象混乱。
解决:
使用volatile要求对一个变量的写操作先行发生于后面对这个变量的读操作
public class Singleton {
private static volatile Singleton stu=null;
private Singleton() {
}
public static Singleton getInstance() {
if(stu==null){
synchronized (""){
if(stu==null){
return new Singleton();
}
}
}
return stu;
}
}
讲指令重排,分析对象错乱原因
8、连环发问5:能否应对序列化造成的影响?
分析:
序列化后多次反序列化得到的多个对象时不等的。
解决:
对象声明为static,禁止序列化。
讲序列化底层
9、连环发问6:能否应对反射造成的影响?
分析:
反射创建对象不受私有化空构造器的影响
解决:
类声明为abstract,本类无法实例化,借助匿名子类完成实例化
匿名子类不定义任何实质代码,调用方法和属性找的仍然是父类中的。
public abstract class Singleton {
private static volatile Singleton stu=null;
private Singleton() {
}
public static Singleton getInstance() {
if(stu==null){
synchronized (""){
if(stu==null){
return new Singleton(){};
}
}
}
return stu;
}
}
讲abstract底层和匿名内部类原理
9、连环发问7:匿名子类是否唯一?
分析:
private限制了外部的创建匿名子类
10、连环发问8:唯一的匿名子类安全吗?
分析:
外界获取唯一匿名子类实例,赋值为null,再次获取就是新的匿名子类对象,所以不安全。
改成饿汉式单例:
public abstract class Singleton {
private static final Singleton stu=new Singleton(){};
private Singleton() {
}
}
将final和volatile不能共存理由
volatile 是保证变量被写时其结果其他线程可见,final 已经让该变量不能被再次写了。
11、适用性:引出枚举单例
分析:
static 禁止了序列化,如果唯一的匿名子类需要序列化就不适用了
枚举式单例:
public enum Student {
STU()
}
public enum Student {
STU("lucy");
private String name;
private Student(String name) {
this.name=name;
}
}
将枚举单例如何解决多线程并发、反射、序列化、安全性问题。
参考:基于反射、序列化、多线程及单例安全性角度看常用的三种JAVA单例模式