如何创建单例模式?

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


前言

单例模式(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) 如果实例化的共享对象长时间不被利用,可能会被自动垃圾回收机制回收,导致共享的单例对象状态的丢失。

总结

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

### 单例模式的定义 单例模式(Singleton Pattern)是一种创建型设计模式,其核心目标是确保一个类只有一个,并提供一个全局访问点[^1]。这种模式通常用于需要控制资源的情况下,比如数据库连接池、线程池或者配置管理器。 --- ### C#中的单例模式实现方法 以下是几种常见的单例模式实现方式: #### 方法一:懒汉式单例模式 在这种实现中,对象仅在第一次调用 `Instance` 属性时被创建。这种方法利用了静态字段和锁机制来保证线程安全。 ```csharp public class Singleton { private static readonly object _lock = new object(); private static Singleton _instance; private Singleton() { } public static Singleton Instance { get { lock (_lock) { if (_instance == null) _instance = new Singleton(); return _instance; } } } } ``` 此方法适用于延迟加载的情况,但在多线程环境下性能可能受到影响[^1]。 --- #### 方法二:饿汉式单例模式 该方法在类加载时就初始化实,因此不存在线程安全问题。然而,它不支持延迟加载。 ```csharp public class Singleton { private static readonly Singleton _instance = new Singleton(); private Singleton() { } public static Singleton Instance => _instance; } ``` 由于实化发生在类加载阶段,饿汉式单例模式非常简洁且高效[^2]。 --- #### 方法三:双重锁定机制 (Double-Checked Locking) 这是一种优化后的懒汉式单例模式,在减少锁开销的同时保持线程安全性。 ```csharp public class Singleton { private volatile static Singleton _instance; private static readonly object _lock = new object(); private Singleton() { } public static Singleton Instance { get { if (_instance == null) { lock (_lock) { if (_instance == null) _instance = new Singleton(); } } return _instance; } } } ``` 通过两次检查 `_instance` 是否为空,减少了不必要的同步操作,从而提高了效率。 --- #### 方法四:基于枚举的单例模式 这是《Effective Java》推荐的一种实现方式,具有天然的序列化保护和反射攻击防护能力。 ```csharp public enum Singleton { INSTANCE; public void DoSomething() { Console.WriteLine("Action performed."); } } // 使用示 Singleton.INSTANCE.DoSomething(); ``` 枚举类型的单例模式不仅代码简,还能够自动处理反序列化的复杂情况[^3]。 --- ### 总结 以上介绍了四种不同的单例模式实现方法,分别是懒汉式、饿汉式、双重锁定机制以及基于枚举的方式。每种方法各有特点,开发者可以根据实际需求选择最合适的方案。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值