两个饿汉,六个懒汉都想实现单例模式

  • 其中一个饿汉创建静态变量时就直接初始化
  • 另一个饿汉创建静态变量后在静态代码块中初始化
  • 懒汉一号想在获取实例的静态方法中初始化,低级操作,线程不安全
  • 懒汉二号想在获取实例的静态方法中初始化,一言不合就把整个方法锁住,效率低
  • 懒汉三号想在获取实例的静态方法中初始化,把初始化代码锁住了,线程还是不安全
  • 懒汉四号想在获取实例的静态方法中初始化,采用双重检查,并保证了创建对象是原子操作,拿去装逼简直妙
  • 懒汉五号创建了个静态内部类进行初始化,方便快捷
  • 懒汉六号直接采用枚举方式,杜绝一切花里胡哨,生产必备!

下面,让我们一起去了解下单例模式的实现

为什么需要单例模式
  • 节省内存和计算
  • 保证结果正确
  • 方便管理
单例模式适用场景
  • 无状态的工具类:如日志工具类
  • 全局信息类:如网站访问次数记录类
饿汉式(静态常量)(可用)
/** * 〈饿汉式(静态常量)〉 * 可用 * @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面试核心知识点汇总》查看目录和直达链接