单例模式
1. 单例的定义❓
一个类只允许常见一个对象(或者实例),那这个类就是一个单例类
2. 单例的用处❗
从业务概念上,有些数据在系统中值应该保存一份,就比较适合设计为单例类。比如 ,系统 的配置信息类 ,除此之外。我们还可以使用单例解决资源访问冲突的问题。
3. 单例的实现 ❗
- 饿汉式
在类加载的期间,就已经将instance 静态实例初始化好了,所以,instance实例的创建是线程安全的。不过,这样的实现方式不支持延迟加载实例
public sealed class EagerSingleton
{
// 静态字段在类加载时初始化(线程安全)
private static readonly EagerSingleton _instance = new EagerSingleton();
// 私有构造函数,防止外部实例化
private EagerSingleton() { }
// 提供全局访问点
public static EagerSingleton Instance => _instance;
}
- 懒汉式
懒汉式相对于饿汉式的优势是支持延迟加载,这种实现方式会导致频繁枷锁,释放锁,以及并发度低等问题,频繁的调用会产生性能瓶颈。
public sealed class EagerSingleton
{
// 静态字段在类加载时初始化(线程安全)
private static readonly EagerSingleton _instance = new EagerSingleton();
// 私有构造函数,防止外部实例化
private EagerSingleton() { }
// 提供全局访问点
public static EagerSingleton Instance => _instance;
}
- 线程安全式
缺点:每次访问 Instance 都会加锁,性能较差。
public sealed class LockedLazySingleton
{
private static LockedLazySingleton _instance;
private static readonly object _lock = new object();
private LockedLazySingleton() { }
public static LockedLazySingleton Instance
{
get
{
lock (_lock) // 加锁保证线程安全
{
if (_instance == null)
{
_instance = new LockedLazySingleton();
}
return _instance;
}
}
}
}
- 双重加锁
volatile 防止指令重排序(确保其他线程看到完全初始化的实例)。
只有第一次初始化时加锁,后续访问无锁,性能最优。
public sealed class DoubleCheckedLockingSingleton
{
private static volatile DoubleCheckedLockingSingleton _instance;
private static readonly object _lock = new object();
private DoubleCheckedLockingSingleton() { }
public static DoubleCheckedLockingSingleton Instance
{
get
{
if (_instance == null) // 第一次检查(无锁)
{
lock (_lock)
{
if (_instance == null) // 第二次检查(避免竞争)
{
_instance = new DoubleCheckedLockingSingleton();
}
}
}
return _instance;
}
}
}
4. 单例存在哪些问题❓
- 单例对OOP 特性支持不友好
- 单例会隐藏类之间的依赖关系
- 单例对代码的扩展性不友好
- 单例对代码的可测试性不友好
- 单例不支持有参数的构造函数
5. 单例有什么替代的解决方案❓
为了保证全局唯一 ,除了使用单例,我们还可以用静态方法来实现,不过,静态方法这种实现思路,并不能解决全部的问题。可以用工厂模式,IOC容器,来保证,由程序员自己来保证(自己在编写代码的时候不要创建两个类对象)
如果单例类并没有后续扩展的需求,并且不依赖外部系统,那设计成单例类就没有太大问题
6. 如果理解单例模式的唯一性❓
单例类中对象的唯一性的作用范围是“进程唯一”的,进程唯一指的是进程内唯一,进程间不唯一;“线程唯一”值得是线程内唯一,线程间可以不唯一,实际上,“进程唯一” 就以为折线程内,线程间都唯一,这也是“进程唯一”和“线程唯一”的区别之外。“集群唯一”指的是进程内唯一,进程间也唯一。
7. 如何实现线程唯一的单例。❓
可以通过一个HashMap 来存储对象,其中Key 是线程ID ,value 是对象。这样我们就可以做到,不同的线程对象不同的对象,同一个线程只能对应一个对象。
8. 如何实现集群环境下的单例 ❓
我们需要把这个单例对象序列化并存储到外部共享存储区(比如文件)。进程在使用这个单例对象的时候,需要先从外部共享存储区中将它读取到内存,并反序列化成对象,然后再使用,使用完成之后还需要再存储回外部共享存储区。为了保证任何时刻在进程间都只有一份对象存在,一个进程在获取到对象之后,需要对对象加锁,避免其他进程再将其获取。在进程使用完这个对象之后,需要显式地将对象从内存中删除,并且释放对对象的加锁
9. 如何实现一个多例模式 ❓
“单例”指的是一个类只能创建一个对象。对应地,“多例”指的就是一个类可以创建多个对象,但是个数是有限制的,比如只能创建 3 个对象。多例的实现也比较简单,通过一个 Map 来存储对象类型和对象之间的对应关系,来控制对象的个数。