单例模式介绍:饿汉式与懒汉式的实现及多线程问题?

网安智编 厦门萤点网络科技 2025-08-07 00:02 94 0
大家好,我是徒手敲代码。 今天来介绍一下单例模式。 单例模式的意思,就是对于某一个类,只能创建一个实例对象。 饿汉式和懒汉式 首先根据概念,可以写出这样的单例模式代码 demo public class Singleton { //...

大家好,我是徒手敲代码。

今天来介绍一下单例模式。

单例模式的意思,就是对于某一个类,只能创建一个实例对象。

饿汉式和懒汉式

首先根据概念,可以写出这样的单例模式代码 demo

public class Singleton {
    // 私有构造函数,严格限制入口
    private Singleton() {}  
    // 饿汉式
    private static final Singleton instance = new Singleton();  
    public static Singleton getInstance() {
        return instance;  
    }
}

因为目的是想只能创建一个对象,所以把构造方法写成私有的,然后暴露一个 () 方法出来,让别的类调用这个方法来获取 对象。

像这样还没有实际用到对象之前,就已经将对象创建出来的方式,称为饿汉式。

如果说为了尽可能节省内存的开销,可以在实际需要这个对象的时候,才创建,这种方式也称为懒汉式,代码如下:

public class Singleton {
    // 私有构造函数,严格限制入口
    private Singleton() {}  
    // 懒汉式
    private static Singleton instance = null; 
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();  
        }
        return instance;
    }
}

上面的这段小程序,在多线程环境下,肯定是没办法做到单例的,如果多个线程同时进入 if 判断,同时判断出 为空,那么自然地就可以创建出来多个 对象了。

双重检测锁

在上面懒汉式的基础上,可以在判断的时候,加个锁,只能让一个线程进入 new 对象。比如:

public class Singleton {
    // 私有构造函数,严格限制入口
    private Singleton() {}  
    private static Singleton instance = null;  
    public static Singleton getInstance() {
        // 第一次检查
        if (instance == null) {      
            //加锁
            synchronized (Singleton.class) {
                // 第二次检查
                if (instance == null) {     
                    instance = new Singleton();  
                }
            }
        }
        return instance;
    }
}

这里锁的是这个的类,注意不能使用对象锁哈,因为我们目的是整个类只能创建一个对象。

为什么要做第二次检查?假设 A 线程刚刚完成了对象的构建,此时有个 B 线程进来,在刚即将创建完成和创建完成的,这个临界点,B是可以获取到锁,进去 new 对象的,所以说,一定要加多一次判断。

指令重排序

其实这段代码,也不是百分百的线程安全。编译器在将我们写的代码,转换成字节码的过程中,会趁我们不注意,来一个指令重排序的操作。

比如 = new (); 这个操作,在计算机的眼里,总共有这三个步骤:

我们会一厢情愿的认为,执行顺序就是 a → b → c

但是,实际情况是,有可能操作顺序变成 a → c → b,那么就存在一个时刻,是 变量不为空,但是对象还没有创建出来;在这个时刻,如何恰巧有一个线程进来,那么就直接去到 ;返回了一个处于无效状态的对象。天啊,辛辛苦苦搞过来的对象,居然是无效的!

写一个singleton出来_单例模式实现方法_懒汉式单例线程安全

单例模式实现方法_懒汉式单例线程安全_写一个singleton出来

我们在之前的文章讲过,要打破这种情况,就要加上一个,加了之后的代码如下:

public class Singleton {
    // 私有构造函数,严格限制入口
    private Singleton() {}  
    private volatile static Singleton instance = null;  
    public static Singleton getInstance() {
        // 第一次检查
        if (instance == null) {      
            //加锁
            synchronized (Singleton.class) {
                // 第二次检查
                if (instance == null) {     
                    instance = new Singleton();  
                }
            }
        }
        return instance;
    }
}

其他方式

其实还有其他实现单例模式的常见方法,比如用静态内部类

public class Singleton {
    // 私有构造函数,严格限制入口
    private Singleton() {} 
    private static class SingletonHolder {
        // 静态内部类存放对象实例
        private static final Singleton INSTANCE = new Singleton();  
    }
    public static Singleton getInstance() {
        // 调用时才加载静态内部类
        return SingletonHolder.INSTANCE;  
    }
}

注意,这种方式,从外部是无法访问内部类的,只能调用()方法来获取实例对象。

以上说的这些实现单例模式的方法,都可以用反射来破解,大致步骤是:

代码:

        //获得构造器
        Constructor constructor = Singleton.class.getDeclaredConstructor();
        //开启访问权限
        constructor.setAccessible(true);
        //构造两个不同的对象
        Singleton singleton1 = (Singleton)constructor.newInstance();
        Singleton singleton2 = (Singleton)constructor.newInstance();
        //验证是否是不同对象
        System.out.println(singleton1.equals(singleton2));

可发现结果为 false

如果要防止被反射打破单例的实现,可以用枚举的方式,代码就一行:

public enum SingletonEnum {
    INSTANCE;
}

总结一下以上这几种方式的特点

懒汉式单例线程安全_单例模式实现方法_写一个singleton出来

三种实现单例模式的方法对比

今天的分享到这里结束了,如果你喜欢这种分享知识的方式,可以在下方留言喔。

——————————————————