主题
面试速答(先看这里)
**一句话结论:**如果不能使用synchronized和lock的话,想要实现单例可以通过饿汉模式、枚举、以及静态内部类的方式实现。
60秒标准回答:
如果不能使用synchronized和lock的话,想要实现单例可以通过饿汉模式、枚举、以及静态内部类的方式实现
饿汉 ,其实都是通过定义静态的成员变量,以保证instance可以在类初始化的时候被实例化
静态内部类 ,这种方式和饿汉方式只有细微差别,只是做法上稍微优雅一点。这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。。。但是,原理和饿汉一样
**答题顺序:**结论 → 原理/机制 → 关键流程 → 场景与取舍 → 易错点
回答主线:
- **要点1:**饿汉 ,其实都是通过定义静态的成员变量,以保证instance可以在类初始化的时候被实例化。
- **要点2:**静态内部类 ,这种方式和饿汉方式只有细微差别,只是做法上稍微优雅一点。
- **要点3:**其实,上面三种方式,都是依赖静态数据在类初始化的过程中被实例化这一机制的。
- **要点4:**那么,除了上面这三种,还有一种无锁的实现方式,那就是CAS。
- **要点5:**用CAS的好处在于不需要使用传统的锁机制来保证线程安全。
**记忆锚点:**instance → CAS → SingletonHolder → synchronized → 静态内部类 → getInstance
易错提醒:
- 如果不能使用synchronized和lock的话,想要实现单例可以通过饿汉模式、枚举、以及静态内部类的方式实现。
加分表达:
- 枚举 ,其实,如果把枚举类进行反编译,你会发现他也是使用了static final来修饰每一个枚举项。
- 但是,如果真要较真的话,ClassLoader的loadClass方法在加载类的时候使用了synchronized关键字。
追问准备:
- 围绕「instance」:底层原理是什么?使用时有哪些边界和常见坑?
- 围绕「CAS」:底层原理是什么?使用时有哪些边界和常见坑?
- 围绕「SingletonHolder」:底层原理是什么?使用时有哪些边界和常见坑?
- 如果线上出现异常,你会如何定位、验证并规避?
典型回答
如果不能使用synchronized和lock的话,想要实现单例可以通过饿汉模式、枚举、以及静态内部类的方式实现。
饿汉,其实都是通过定义静态的成员变量,以保证instance可以在类初始化的时候被实例化。
静态内部类,这种方式和饿汉方式只有细微差别,只是做法上稍微优雅一点。这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。。。但是,原理和饿汉一样。
枚举,其实,如果把枚举类进行反编译,你会发现他也是使用了static final来修饰每一个枚举项。
其实,上面三种方式,都是依赖静态数据在类初始化的过程中被实例化这一机制的。但是,如果真要较真的话,ClassLoader的loadClass方法在加载类的时候使用了synchronized关键字。也正是因为这样, 除非被重写,这个方法默认在整个装载过程中都是同步的(线程安全的)。
那么,除了上面这三种,还有一种无锁的实现方式,那就是CAS。
扩展知识
CAS实现线程安全的单例
plain
public class Singleton {
private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>();
private Singleton() {}
public static Singleton getInstance() {
for (;;) {
Singleton singleton = INSTANCE.get();
if (null != singleton) {
return singleton;
}
singleton = new Singleton();
if (INSTANCE.compareAndSet(null, singleton)) {
return singleton;
}
}
}
}用CAS的好处在于不需要使用传统的锁机制来保证线程安全。
但是我们的实现方式中,用了一个for循环一直在进行重试,所以,这种方式有一个比较大的缺点在于,如果忙等待一直执行不成功(一直在死循环中),会对CPU造成较大的执行开销。