设计模式 -- 单例模式(多线程)
上一节,我们给出了单例的最简单的实现,这一节我们将解决上一节提出的那个问题----多线程环境下如何运用单例?
解决方案1、同步锁
我们在创建对象之前,先加锁,这样创建对象的那部分部分代码就不会被两个线程执行了。
代码:
public class Singleton { private static Singleton uniqueInstance; private static readonly object syncRoot = new object();//同步锁 private Singleton() { } public static Singleton getInstance() { //lock确保当一个线程位于临界区时,另外的线程不能进如临界区 //也就是在同一时刻,加了锁的那部分程序只有一个线程能够进去 lock(syncRoot) { if (uniqueInstance == null) { uniqueInstance = new Singleton(); } } return uniqueInstance; } }
可以看到,我们唯一做出的修改就是给这个类加了一个同步锁,然后在getInstance方法中创建对象之前先加锁。这很容易理解吧?但是,这种方法是有缺陷的,这里我们用一重锁定来实现多线程的单例,但是由于我们采用同步,每次执行 这个getInstance()方法时都需要判断同步锁,而实际上我们只有在第一次执行时 判断就够了,这也就说这种解决办法会大大降低程序的性能。那么我们在一起寻找其他的更好的解决方案吧。
解决方案2、双重锁
双重锁就是在创建对象之前加双重锁(实际上三重),首先我们先给创建对象的代码加锁,然后再判断唯一的实例是否已经寻在,如果不存在我们创建对象。
代码:
public static Singleton getInstace() { //当两个线程同时“冲击”到这一步时,都可以通过判断进入 if (uniqueInstance == null) { //这时就只有一个线程可以进入,两一个线程排队等候,必须要一个线程进入并出来后,第二个线程才开可以进入 lock (syncRoot) { //为什么还要继续判断呢?如果没有这个判断 ,当两个线程都冲过第一关,而 //第一个线程又创建了实例,放开锁后,那么第二个线程还是可以继续创建实例 if (uniqueInstance == null) { uniqueInstance = new Singleton(); } } } return uniqueInstance; } }
第一个 if判断:如果两个线程先后执行到这一步,这时uniqueInstance是null的,那么两个线程都能进入这个判断。
第二个 加锁:这个是给创建对象的代码加锁,防止两个线程同时访问uniqueInstance
第三个 if判断:你可能会疑问为什么还要加一个这个判断?不多余吗?不多于啊。看看代码注视吧。
解决方案3:静态初始化(不推荐使用)
“静态初始化”方法是由“C#”和“公共语言运行库”提供的,他不需要开发人员显示的编写多线程安全代码,即可解决多线程环境问题。
代码:
public sealed class Singleton//sealed关键字防止该类派生子类,因为派生可能会增加实例 { //在第一次引用类的任何成员时创建实例。公共语言运行库负责处理变量初始化 private static readonly Singleton uniqueInstance = new Singleton(); private Singleton() { } public static Singleton getInstance() { return uniqueInstance; } }
总结:我们可以看到,无论是多线程还是单线程环境下的单例,类的结构都是相同的,首先,类要有一个私有的被类别的实例;然后,要有一个私有构造器;最后,提供一个全局访问点。这三点是必不可少的。把握住这三个点来写单例就没问题了。