在上一篇博客中(https://www.cnblogs.com/t140603/p/10318228.html)提到的第二个单例模式的实例为:
public class SingLetonClass
{
//定义一个用于保存静态变量的实例
private static SingLetonClass instance = null;
//定义一个保证线程同步的标识
private static readonly object locker = new object();
//构造函数为私有,使外界不能创建该类的实例
private SingLetonClass()
{
}
public static SingLetonClass GetInstance()
{
lock (locker)
{
if (instance == null)
{
SingLetonClass instance = new SingLetonClass();
}
}
return instance;
}
}
然而这个实现方式虽然是安全的,但是是不是最好的呢,还能不能再次进行优化?
下面就分析一下多线程中单例模式的运行:
当程序的线程执行到锁时,就会停下来判断是否有其他线程在执行锁里面的代码,没有才会进入继续执行,那么如果多条线程同时运行到锁的时候就只能一条一条进入锁,进入并判断是否已被实例化,如有就跳出,如无,则进行实例化。
上面,我们对多线程中的单例模式进行了分析,从分析中我们可以看出,每条线程都要先进入锁才做判断,但在没进入之前,线程就已经可以判断出是否有过实例,那为什么我们不在外面就先进行判断再进入锁呢?下面就给出优化后的代码:即再锁的外面多加一次判断。
public class SingLetonClass
{
//定义一个用于保存静态变量的实例
private static SingLetonClass instance = null;
//定义一个保证线程同步的标识
private static readonly object locker = new object();
//构造函数为私有,使外界不能创建该类的实例
private SingLetonClass()
{
}
public static SingLetonClass GetInstance()
{
if (instance == null)
{
lock (locker)
{
if (instance == null)
{
SingLetonClass instance = new SingLetonClass();
}
}
return instance;
}
}
}
从更改后的代码上来看,用了两个if进行判断的原因还是在于多线程的特性,还是因为多线程访问函数时都是独立访问的,所以才要用到两个if(如果对多线程特性不太了解的童鞋,可自行查资料研究一下)。
先分析内层的if语句块,在这个语句块执行之前,先进行了加锁操作,从而保证只有一个线程能够访问该语句块,也就是保证只创建了一个实例。再分析外层的if语句块,这使得每个线程欲获取实例时不必每次都得加锁,因为只有实例为空时(即需要创建一个实例),才需加锁创建,若果已存在一个实例,就直接返回该实例,这样就节省了性能开销。
这个过程实际上不是双重锁,只是进行了两次判断,因为真正的锁机制代价是比较高的,所以本文介绍的机制可以称为双检锁机制。