2020-11-11
单例模式的理解与实现
一、单例模式的优缺点
1.单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。
2.单例模式的缺点就是不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
用单例模式,就是在适用其优点的状态下使用。
二、单例模式的简介
什么是单例模式?从“单例”字面意思上理解为—一个类只有一个实例,所以单例模式也就是保证一个类只有一个实例的一种实现方法罢了。(设计模式其实就是帮助我们解决实际开发过程中的方法,该方法是为了降低对象之间的耦合度,然而解决方法有很多种,所以前人就总结了一些常用的解决方法为书籍,从而把这本书就称为设计模式)。
单例模式的官方定义:确保一个类只有一个实例,并提供一个全局访问点。
单例模式的一般实现为:
Singleton类通过定义一个私有变量uniqueInstance来记录单例类的唯一实例;
私有方法Singleton()来防止外界使用new关键字来创建该类实例;
公有方法GetInstance()来提供该类实例的唯一全局访问点。
public class Singleton
{
//私有变量来记录Singleton的唯一实例
private static Singleton uniqueInstance;
//私有构造函数
private Singleton()
{}
//定义公有方法来提供该类的唯一全局访问点
public static Singleton GetInstance()
{
//如实例不存在,则new一个新实例,否则返回已有实例
if(uniqueInstance == null)
{
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
上面的单例模式的实现在单线程下确实是完美的,然而在多线程的情况下会得到多个Singleton实例,因为在两个线程同时运行GetInstance方法时,此时两个线程判断(uniqueInstance == null)这个条件时都返回真,此时两个线程就都会创建Singleton的实例,这样就违背了我们单例模式初衷了,既然上面的实现会运行多个线程执行,那我们对于多线程的解决方案自然就是使GetInstance方法在同一时间只运行一个线程就好了,也就是我们线程同步的问题了,具体解决多线程的代码如下:
//单例模式的实现
public class Singleton
{
//定义一个静态变量来保存类的实例
private static Singleton uniqueInstance;
//定义一个标识,确保线程同步
private static readonly object locker = new object();
//定义私有构造函数,使外界不能创建该类实例
private Singleton()
{}
//定义公有方法提供一个全局访问点,同时也可以定义公有属性来提供全局访问点
public static Singleton GetInstance()
{
//当第一线程运行到这里时,此时会对locker对象“加锁”,
//当第二个线程运行该方法时,首先检测到locker对象为“加锁”状态,该线程就会挂起等待第一个线程解锁
//locker语句运行完之后(即线程运行完之后)会对该对象“解锁”
lock(locker)
{
//如果类的实例不存在则创建,否则直接返回
if(uniqueInstance == null)
{
uniqueInstance = new Singleton();
}
}
return uniqueInstance;
}
}
上面这种解决方案确实可以解决多线程的问题,但是上面代码对于每个线程都会对线程辅助对象locker加锁之后再判断实例是否存在,对于这个操作完全没有必要的,因为当第一个线程创建了该类的实例之后,后面的线程此时只需要直接判断(uniqueInstance == null)为假,此时完全没必要对线程辅助对象加锁之后再去判断,所以上面的实现方式增加了额外的开销,损失了性能,为了改进上面实现方式时缺陷,只需要在lock语句前面加一句(uniqueInstance == null)的判断就可以避免锁所增加的额外开销,这种实现方式就叫它“双重锁定”,下面具体看看实现的代码:
//单例模式的实现
public class Singleton
{
//定义一个静态变量来保存类的实例
private static Singleton uniqueInstance;
//定义一个标识确保线程同步
private static readonly object locker = new object();
//定义私有构造函数,使外界不能创建该类实例
private Singleton()
{}
//定义公有方法提供一个全局访问点,同时也可以定义公有属性来提供全局访问点
public static Singleton GetInstance()
{
//当第一个线程运行到这里时,此时会对locker对象“加锁”,
//当第二个线程运行该方法时,首先检测到locker对象为“加锁”状态,该线程就会挂起等待第一个线程解锁
//lock语句运行完成之后(即线程运行完之后)会对该对象“解锁”
//双重锁定只需一句判断就可以了
if(uniqueInstance == null)
{
lock(locker)
{
if(uniqueInstance == null)
{
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
本文地址:https://blog.csdn.net/m0_44998503/article/details/109611624