单例模式的七种写法

为什么使用单例?

避免重复创建对象,节省内存,方便管理;一般我们在工具类中频繁使用单例模式;

1.饿汉式(静态常量)-[可用]

复制代码
/**
 * 饿汉式(静态常量)
 */
public class Singleton1 {

    private static final Singleton1 INSTANCE = new Singleton1();
    private Singleton1(){}

    public static Singleton1 getInstance(){
        return INSTANCE;
    }
}

2.饿汉式(静态代码块)[可用]

复制代码
/**
 * 饿汉式(静态代码块)
 */
public class Singleton2 {

    private static final Singleton2 INSTANCE;

    static {
        INSTANCE = new Singleton2();
    }

    private Singleton2(){}

    public static Singleton2 getInstance(){
        return INSTANCE;
    }
}

3.懒汉式(同步方法)[不推荐]

复制代码
/**
 * 懒汉式(同步方法)
 *      不推荐:效率太低了
 */
public class Singleton3 {

    private static Singleton3 instance;

    private Singleton3(){}

    public synchronized static Singleton3 getInstance(){
        if(instance == null){
            instance = new Singleton3();
        }
        return instance;
    }
}

4.双重检查(有空指针问题)[面试用]

复制代码
/**
 * 双重检查-推荐面试使用
 */
public class Singleton4 {

    private static Singleton4 instance;
    private Singleton4(){}

    public static Singleton4 getInstance(){
        if(instance == null){ // 检查一
            synchronized (Singleton4.class){
                if(instance == null){ // 检查二
                    instance = new Singleton4();
                }
            }
        }
        return instance;
    }
}

双重检查优点:

线程安全,延迟加载,效率较高;

5.双重检查进阶版(volatile)[推荐]

使用volatile目的在于,禁止创建对象时的3个步骤发生重排序,防止创建出的对象空指针问题;

复制代码
/**
 * 双重检查 + volatile(禁止创建对象时3个步骤执行流程重排序,防止创建出的对象空指针问题)
 */
public class Singleton5 {
    // 加上volatile防止新建对象时重排序带来的"空指针"问题
    private volatile static Singleton5 instance;

    private Singleton5(){}

    public static Singleton5 getInstance(){
        if(instance == null){ // 检查一
            synchronized (Singleton5.class){

                /**
                 * 为什么使用volatile修饰instance?
                 *      1.新建对象操作不是原子性操作,其由三个操作构成(instance = new Singleton5(););
                 *          1.1.创建一个空的instance;
                 *          1.2.调用构造方法;
                 *          1.3.将创建好的对象赋值给instance实例;
                 *      2.因为创建对象操作不是原子性操作,所以使用volatile来禁止创建对象时重排序
                 *      3.如果不使用volatile修饰instance实例,则创建对象时被JVM重排序后的执行流程可能如下(即出现"空指针问题")
                 *          1.2 -> 1.1 -> 1.3
                 */
                if(instance == null){ // 检查二
                    instance = new Singleton5();
                }
            }
        }
        return instance;
    }
}

6.静态内部类[推荐]

复制代码
 * 静态内部类(可用)
 *      原理:
 *          外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE故不占内存。
 *          只有当getInstance()方法第一次被调用时才会去初始化INSTANCE,第一次调用getInstance()方法才会使虚拟机加载SingletonInstance类(实现懒加载)
 *          因为SingletonInstance类中INSTANCE实例是被static final修饰所以只会被初始化一次,后续调用会直接返回实例不需要进行同步操作;
 */
public class Singleton6 {

    private Singleton6(){}

    private static class SingletonInstance{
        private static final Singleton6 INSTANCE = new Singleton6();
    }

    public static Singleton6 getInstance(){
        return SingletonInstance.INSTANCE;
    }
}

7.枚举[推荐(开发中使用最好)]

复制代码
/**
 * 枚举
 */
public enum  Singleton7 {
    INSTANCE;

    public void todoSomething(){
        System.out.println("我是枚举实现单例模式中的一个普通业务方法...");
    }

    // 调用
    public static void main(String[] args) {
        Singleton7.INSTANCE.todoSomething();
    }
}

8.单例模式面试题

8.1.饿汉式的缺点?

没有实现懒加载,容易造成资源的浪费;

8.2.懒汉式的缺点?

为保证线程安全使用同步方法效率低;

8.3.为什么使用"双重检查"不用就不安全吗?

"双重检查"代码中只做第一次检查是线程不安全的;容易创建多个对象。若不用双重检查也可以使用synchronized去修饰获取实例方法来保证线程安全(类似于懒汉式的同步方法);

8.4.双重检查为什么要使用volatile?

1. 新建对象操作不是原子性操作,其由三个操作构成(比如: instance = new Singleton5(););

1.1.创建一个空的instance;

1.2.调用构造方法;

1.3.将创建好的对象赋值给instance实例;
2. 因为创建对象操作不是原子性操作,所以使用volatile来禁止创建对象时JVM重排序问题;
3. 如果不使用volatile修饰instance实例,则创建对象时被JVM重排序后的执行流程可能如下("空指针问题"),若创建出来的对象为null,由于"可见性"问题,下次去获取实例时还是会创建多个对象;
JVM重排序后执行流程可能为:(即出现空指针问题)

1.2 -> 1.1 -> 1.3

8.5.在生产中用哪种单例的实现方案最好?

使用枚举的方式实现单例最好;

**1.**写法简单;

**2.**线程安全;

**原因:**枚举类会被JVM编译成final修饰的class其继承了枚举这个父类,在父类中各个实例都是

使用static定义的,所以枚举的本质就是一个静态编译的对象;

**3.**懒加载;

**4.**防止反序列化重新创建对象;

相关推荐
CryptoRzz3 分钟前
欧美(美股、加拿大股票、墨西哥股票)股票数据接口文档
java·服务器·开发语言·数据库·区块链
杂货铺的小掌柜22 分钟前
apache poi excel 字体数量限制
java·excel·poi
Never_Satisfied25 分钟前
在JavaScript / HTML中,div容器在内容过多时不显示超出的部分
开发语言·javascript·html
大厂码农老A31 分钟前
你打的日志,正在拖垮你的系统:从P4小白到P7专家都是怎么打日志的?
java·前端·后端
艾菜籽1 小时前
Spring MVC入门补充2
java·spring·mvc
艾莉丝努力练剑1 小时前
【C++STL :stack && queue (一) 】STL:stack与queue全解析|深入使用(附高频算法题详解)
linux·开发语言·数据结构·c++·算法
爆更小哇1 小时前
统一功能处理
java·spring boot
程序员鱼皮1 小时前
我造了个程序员练兵场,专治技术焦虑症!
java·计算机·程序员·编程·自学
胡萝卜3.01 小时前
深入理解string底层:手写高效字符串类
开发语言·c++·学习·学习笔记·string类·string模拟实现
n8n1 小时前
SpringAI 完全指南:为Java应用注入生成式AI能力
java·后端