什么是单例模式
对象重用的需求:
- 对象在构造的时候会消耗时间或者占用资源
- 很多地方都需要用到这个对象
- 不同地方的使用一个实例和多个实例没有区别
对象重用的实现:
- 同一个方法或同一个类:变量共享即可
- 不同类:公开静态变量共享即可
但是,并没有强制保证只有一个实例
强制保证某个类型只有一个实例,这就是单例。
,一个进程只需要一个实例,多个实例反而可能出问题
单例和普通实例的比较
- 对象覆盖:单例会覆盖对象,多次调用会互相影响。而普通实例多次调用互不干扰
- 线程安全:单例并不能保证实例中的方法是线程安全的,即单例、普通实例都不是线程安全的
- 对象个数:单例只有1个对象进行重用,普通实例多个对象,即时创建
- 内存占用:单例常驻内存,普通实例用完释放
- 易用性:单例不能new(),普通实例直接new()
单线程的单例模式
- 私有化构造函数--避免外界重复构造
- 公开静态方法提供实例--这样外界才能使用
- 静态变量共享--保证全局唯一
public class Sinleton {
private Sinleton() {
//...
}
private static Sinleton Instance=null;
public static Sinleton CreateInstance() {
if(Instance==null)
Instance = new Sinleton();
return Instance;
}
}
上端调用
Sinleton sinleton = Sinleton.CreateInstance();
多线程的单例模式
上面的实例,在多线程下仍然会重复构造,因为并发了,在对象被初始化之前,判断失效,所以会被重复初始化
总结:推荐采用静态构造方法或静态字段的方式
方案一:加锁
//1、定义一个readonly的object对象
private static readonly object Sinleton_Lock=new object();
//2、将构造对象的语句加锁
lock (Sinleton_Lock) {//保证方法块只有一个线程可以进入
if (Instance == null)
Instance = new Sinleton();
}
上端,多线程调用
//启动线程
Task.Run(() => {
Sinleton sinleton = Sinleton.CreateInstance();
});
缺点:这其实是担心并发安全就拒绝多线程,又重回单线程,并不是最优的解决方案
方案二:双判断锁
这是一个很经典的写法,但写起来较复杂
private static readonly object Sinleton_Lock=new object();
public static Sinleton CreateInstance() {
if (Instance == null) {//双检锁:对象已经实例化后,不再等待锁
lock (Sinleton_Lock) {//保证方法块只有一个线程可以进入
if (Instance == null)
Instance = new Sinleton();
}
}
return Instance;
}
懒汉式、饿汉式
懒汉式:用到这个实例的时候,才会被构造,即双检锁
饿汉式:只要使用类中的任何东西,类的实例都会被构造,即静态构造函数、静态字段
方案三:静态构造函数
静态构造函数,由CLR调用,在类型第一次被使用前调用,且只调用一次。在单线程、多线程下都生效
public class Sinleton {
private Sinleton() {
//...
}
private static Sinleton Instance=null;
static Sinleton() {
Instance = new Sinleton();
}
public static Sinleton CreateInstance() {
return Instance;
}
}
方案四:静态字段
静态字段,由CLR调用,在类型第一次被使用前初始化,且只初始化一次!
静态字段先构造,静态构造函数再构造
public class Sinleton {
private Sinleton() {
//...
}
private static Sinleton Instance= new Sinleton();
public static Sinleton CreateInstance() {
return Instance;
}
}