Java面试-通过单例模式的8种写法搞定单例模式面试
两个饿汉,六个懒汉都想实现单例模式
- 其中一个饿汉创建静态变量时就直接初始化
- 另一个饿汉创建静态变量后在静态代码块中初始化
- 懒汉一号想在获取实例的静态方法中初始化,低级操作,线程不安全
- 懒汉二号想在获取实例的静态方法中初始化,一言不合就把整个方法锁住,效率低
- 懒汉三号想在获取实例的静态方法中初始化,把初始化代码锁住了,线程还是不安全
- 懒汉四号想在获取实例的静态方法中初始化,采用双重检查,并保证了创建对象是原子操作,拿去装逼简直妙!
- 懒汉五号创建了个静态内部类进行初始化,方便快捷
- 懒汉六号直接采用枚举方式,杜绝一切花里胡哨,生产必备!
下面,让我们一起去了解下单例模式的实现
快速到达看这里-->
为什么需要单例模式
- 节省内存和计算
- 保证结果正确
- 方便管理
单例模式适用场景
- 无状态的工具类:如日志工具类
- 全局信息类:如网站访问次数记录类
饿汉式(静态常量)(可用)
/** * 〈饿汉式(静态常量)〉 * 可用 * @author Chkl * @create 2020/3/4 * @since 1.0.0 */
public class Singleton1 {
private final static Singleton1 INSTANCE =
new Singleton1();
private Singleton1(){
}
public static Singleton1 getInstance(){
return INSTANCE;
}
}
饿汉式(静态代码块)(可用)
/** * 〈饿汉式(静态代码块)〉 * 可用 * * @author Chkl * @create 2020/3/4 * @since 1.0.0 */
public class Singleton2 {
private final static Singleton2 INSTANCE;
static {
INSTANCE = new Singleton2();
}
private Singleton2() {
}
public static Singleton2 getInstance() {
return INSTANCE;
}
}
懒汉式(线程不安全)(不可用)
/** * 〈懒汉式(线程不安全)〉 * 不可用 * * @author Chkl * @create 2020/3/4 * @since 1.0.0 */
public class Singleton3 {
private static Singleton3 instance;
private Singleton3() {
}
public static Singleton3 getInstance() {
//多线程下可能多次创建实例,就不是单例了
if (instance == null) {
instance = new Singleton3();
}
return instance;
}
}
懒汉式(线程安全)(不推荐)
/** * 〈懒汉式(线程安全)〉 * 效率低不推荐使用 * * @author Chkl * @create 2020/3/4 * @since 1.0.0 */
public class Singleton4 {
private static Singleton4 instance;
private Singleton4() {
}
//加上synchronized 安全,但是效率低
public synchronized static Singleton4 getInstance() {
if (instance == null) {
instance = new Singleton4();
}
return instance;
}
}
懒汉式(加锁,线程不安全)(不可用)
/** * 〈懒汉式(线程不安全)〉 * 依然不可用 * * @author Chkl * @create 2020/3/4 * @since 1.0.0 */
public class Singleton5 {
private static Singleton5 instance;
private Singleton5() {
}
public static Singleton5 getInstance() {
if (instance == null) {
synchronized (Singleton5.class) {
instance = new Singleton5();
}
}
return instance;
}
}
双重检查(推荐面试使用)(可用)
/** * 懒汉式 * 〈双重检查(推荐面试使用)〉 * 可用 * * @author Chkl * @create 2020/3/4 * @since 1.0.0 */
public class Singleton6 {
private volatile static Singleton6 instance;
private Singleton6() {
}
public static Singleton6 getInstance() {
if (instance == null) {
/* 线程a,b都到了这个位置 a先进入获得锁 初始化instance a释放锁b获得锁进入 发现instance已经初始化了 跳过初始化代码 */
synchronized (Singleton6.class) {
if (instance == null) {
instance = new Singleton6();
}
}
}
return instance;
}
}
双重检查的优点:
- 线程安全
- 延迟加载,效率较高
为什么要使用双重检查,单次检查不行吗:
- 双重检查能保证线程安全
- 如果只是单次检查,第一个检查后可能多个线程都进入等待锁,锁释放之后会重复初始化
为什么要用volatile?
- 新建对象实际上有3个步骤,并不是原子性的
- 创建一个空对象
- 调用构造方法
- 创建好的实例赋值给引用
- 重排序会带来NPE
- 步骤二和步骤三发生重排序,执行到步骤三时实例就不为空了,如果有新线程进来发现不为空就拿去用了,此时的实例是未调用构造方法的不完整的
- 防止重排序
- 保证可见性(初始化完了之后其他线程能马上看到)
静态内部类(推荐用)(可用)
/** * 〈懒汉式(静态内部类)〉 * 推荐使用 * * @author Chkl * @create 2020/3/4 * @since 1.0.0 */
public class Singleton7 {
private Singleton7() {
}
private static class SingletonInstance {
private static final Singleton7 INSTANCE
= new Singleton7();
}
public static Singleton7 getInstance() {
return SingletonInstance.INSTANCE;
}
}
枚举(推荐用)(可用)(生产中最佳写法)
/** * 〈枚举〉 * 推荐使用 * * 调用:Singleton8.INSTANCE.whatever(); * * @author Chkl * @create 2020/3/4 * @since 1.0.0 */
public enum Singleton8 {
INSTANCE;
//其中的方法
public void whatever(){
}
}
哪种单例的实现方案最好啊?
- 枚举最好!
- 《Effective Java》中明确表示枚举是最佳的
- 写法简单
- 线程安全
- 符合懒加载机制
- 避免反序列化破坏单例
饿汉式的缺点
- 资源效率不高
懒汉式的缺点
- 写法复杂
- 容易写成线程不安全
更多Java面试复习笔记和总结可访问我的面试复习专栏《Java面试复习笔记》,或者访问我另一篇博客《Java面试核心知识点汇总》查看目录和直达链接