Java之单例模式的各种实现

本文深入探讨了单例模式的不同实现方式,包括饿汉式、懒汉式及其优化,并对比了枚举实现单例模式的优势。此外,还介绍了如何防止单例模式被反射和反序列化所破坏。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近连续在各种群里、博客里看到单例模式的讨论。根据我的理解总结一下:
先直接说结论:最优雅最简洁最稳的方法是使用枚举实现单例模式。

饿汉式

//无懒加载
//在类加载时初始化唯一的实例对象,由jvm在多线程环境时保证线程安全
//增加了初始化的时间和内存开销
public class SingleDog {
    private static final SingleDog instance = new SingleDog();

    private SingleDog(){}

    public SingleDog getInstance(){
        return instance;
    }
}

懒汉式

//懒加载
//因为实例化在getInstance里执行,所以每次访问instance,都需要获取同步锁,比较笨重;
public class SingleDog {
private static SingleDog instance = null;

private SingleDog(){}

synchronized public static SingleDog getInstance(){
    //判断是否需要对Instance进行初始化
    if(instance == null){
        instance = new SingleDog();
    }
    return instance;

}

懒汉式多线程环境下优化 之 双重检查锁定

//懒加载(双重检查锁定)
//多线程访问时,只在可能需要对Instance进行初始化时获取锁,大多数时候直接读取Instance即可
public class SingleDog {
    //需用volatile关键字保证对这个变量所做的操作是所有线程可见的
    private volatile static SingleDog instance = null;

    private SingleDog(){}

    public static SingleDog getInstance(){
        //判断是否需要对Instance进行初始化
        if(instance == null){
            synchronized(SingleDog.class){
                //对于已经获取到锁的其他线程,再次判断Instance是否需要进行初始化
                if(instance == null){
                    instance = new SingleDog();
                }
            }
        }
        return instance;

    }
//双重检查锁定也不能避免使用重度锁synchronized,在获取和释放锁的过程中会有性能损耗;同时需要两个if判断来确保只有一个实例,程序逻辑比较复杂

懒汉式多线程环境下优化 之 静态内部类实现


//与第一个饿汉式相比,使用一个私有静态内部类来代替SingleDog类,持有instance实例,达到了懒加载的目的。
//摆脱了重度锁synchronized
//那么多线程环境下一个类是否会被初始化多次呢?
//jvm在类加载时会确保线程的安全,如果多个线程去初始化一个类,只会有第一个线程被执行,其他线程都会被阻塞而且不会再次进入到类的初始化中去。同一个类加载器下,一个类只会被初始化一次
public class SingleDog {
    private SingleDog() {}

    private static class SingleHolder{
        private static SingleDog instance = new SingleDog();
    }

    public static SingleDog getInstance(){
        return SingleHolder.instance;
    }
}

有什么办法破坏上述单例模式

反射

虽然构造函数已经被限定为private了,但是只要有构造函数存在,就可以通过Java反射机制获取到它,还能强制性的setAccessble,将其设定为可访问,从而利用构造函数再生成一个新的实例,破坏单例模式。部分代码如下:

        SingleDog s1 = SingleDog.getInstance();
        Class<SingleDog> cls = (Class<SingleDog>) s1.getClass();
        Constructor<SingleDog> cons = cls.getDeclaredConstructor(new Class[] {});
        cons .setAccessible(true);
        SingleDog s2 = cons.newInstance(new Object[] {});
        System.out.println(s1 == s2);//false

反序列化

当将Java对象序列化后,再反序列时,readObject方法会自动创建一个新的实例。天然破坏了被序列化对象的单例模式。
不过这个问题很好解决,只需要在类中添加一个方法就行:

public class SingleDog {
    private SingleDog() {}

    private static class SingleHolder{
        private static SingleDog instance = new SingleDog();
    }

    public static SingleDog getInstance(){
        return SingleHolder.instance;
    }
    //划重点!
    public Object readResolve() {
        return instance;
    }
}

枚举实现单例模式

//枚举实现单例模式
//防反射、防反序列化、线程安全(实例在类初始化期间就已经创建)
//枚举类实际上是继承于java.lang.Enum的一个抽象类,所以是无法用反射获取到其构造方法的
enum SingleDog {
    INSTANCE;
    public static SingleDog getInstance() {
        return INSTANCE;      
    } 
}

相关文章:
深度分析 Java 的枚举类型:枚举的线程安全性及序列化问题
浅谈使用单元素的枚举类型实现单例模式
单例模式那些坑

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值