如何创建单例模式?

本文详细介绍了Java中单例模式的四种实现方式:饿汉式、懒汉式、内部类(IoDH)和枚举。特别强调了懒汉式的双重检查锁定(DCL)作为面试重点,以及枚举作为实现单例的最佳实践。同时,文章讨论了单例模式的优缺点,包括资源利用率、线程安全、加载速度和拓展性等问题。

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


前言

单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。


一、标准饿汉式单例

在类加载的时候就创建单例对象,后续就只需要调用即可。
优点:
1.线程安全:无论如何多线程访问,保证唯一实例
2.调用、反应速度快:因为类加载时就已经创建,可以不加判断地直接调用。
缺点:
1.资源利用率低:有可能创建之后一直没有被调用,导致资源浪费。
2.加载速度慢:因为类加载时就创建单例对象,会影响加载速度。

0.原初饿汉式

class Singleton {
    private static final Singleton instance = new Singleton();
    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }
}

1.静态代码块

适合复杂的实例化,例如实例化类时加载一些外部的文件等

class Singleton {
    private static final Singleton instance;
    private Singleton() {}
    static {
        instance = new Singleton();
    }
    public static Singleton getInstance() {
        return instance;
    }
}

二、标准懒汉式单例

调用实例时再加载。
由于其在类加载时并不自行实例化,这种技术又被称为延迟加载(Lazy Load),即需要的时候再加载实例。
优缺点:自行对比饿汉式。

0.原初懒汉式

刚听说这个概念的同学可能不假思索地写下如下代码。
此时该方法在单线程情况下已经足够了,但并不满足多线程安全。

class Singleton {
    private static Singleton instance = null;

    private Singleton() { }

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

1.方法同步(基本不用)

此时这份代码具有线程安全性,但由于是对方法上锁,所以多线程情况,速度会较慢。

class Singleton {
    private static Singleton instance = null;

    private Singleton() { }

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

2.双重检查锁定(面试重点)

那么基于锁方法这种解决方式太慢,自然而然大佬们就想着优化
如下:

class Singleton {
    private static volatile Singleton instance = null;

    private Singleton() { }

     public static  Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class){
                if (instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

那么这里必须解决几个问题:
1.为啥双重选择?
如果只保留上面的if,将导致锁了跟没锁一样,当两个线程同时通过instance==null后,在锁那里等待,然后同样执行了两次new。即一个线程new,new后由于另一个线程已经有new的资格了,跟在后面new。

if (instance == null) {
   synchronized (Singleton.class){
        instance = new Singleton();
   }
}

2.为啥volatile?

instance = new Singleton();

对于new而言,JVM可能自动做优化,其预期的执行顺序是:
1、为 instance 分配内存空间
2、初始化 instance
3、将 instance 指向分配的内存地址
JVM优化后可能导致变成1-》3-》2,此时,可能导致instance 还没有初始化完成,就已经被另一个线程调用发现非空,从而return了。即一个线程还没new完,另一个线程在return。
而volatile就可以阻止这种优化,当然不可避免的阻止了一些其他相关优化,因此可能导致效率上的降低。

三、IoDH

大佬们不会停下前进的步伐,他们发现有很多相似的需求,于是对底层进行了优化。
直接使用静态内部类提供支持,既满足了延迟加载,又通过JVM机制提供了对线程安全的支持。

class Singleton {

    private Singleton() {
    }

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

四、枚举(最佳实践)

除了new以外,克隆、反射和反序列化都能产生新对象,其中有些会对以上三种实现方式产生破坏,于是大佬们想出了第四种,枚举。
利用JVM机制,保证了单例类不会被反射,并且构造函数只被执行一次。

class Singleton {
    private Singleton(){
    }
    public static enum SingletonEnum {
        SINGLETON;
        private Singleton instance = null;
        private SingletonEnum(){
            instance = new Singleton();
        }
        public Singleton getInstance(){
            return instance;
        }
    }
}

优缺点

优点:
(1) 单例模式提供了对唯一实例的受控访问,可以严格控制客户如何以及何时访问它。

(2) 由于只提供一个对象,可以节约系统资源,避免了频繁创建删除,可以提高系统的性能。

(3) 允许可变数目的实例。基于单例模式我们可以进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例,既节省系统资源,又解决了单例单例对象共享过多有损性能的问题。

缺点:
(1) 由于单例模式中没有抽象层,拓展困难。

(2) 单例类的职责过重,既负责创建又负责具体功能。(违反单一职责原则)

(3) 如果实例化的共享对象长时间不被利用,可能会被自动垃圾回收机制回收,导致共享的单例对象状态的丢失。

总结

总得来看根据是否在类加载时直接加载分为饿汉式和懒汉式,其中饿汉式面试官一般懒得问,重点掌握懒汉式中的双重选择锁,如果只问怎么创建的话,当然还是给他一个枚举

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值