C#中的单例模式

简介:

一般的,设计模式中用到单例模式,代码通常会如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public  sealed  class  Singleton
{
     private  static  Singleton instance= null ;
 
     private  Singleton()
     {
     }
 
     public  static  Singleton Instance
     {
         get
         {
             if  (instance== null )
             {
                 instance =  new  Singleton();
             }
             return  instance;
         }
     }
}

代码比较简单,用到一个公有的静态属性和一个私有的静态字段。并且把构造函数设为私有,防止该类被实例化。

但上述代码在多线程情况下并不可靠。有一种情况下。2个线程在get的时候,都检测到instance==null,因此各自创建了一个Singleton对象,破坏了单例的原则。

因此改进后的代码就是加锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public  sealed  class  Singleton
{
     private  static  Singleton instance =  null ;
     private  static  readonly  object  padlock =  new  object ();
 
     Singleton()
     {
     }
 
     public  static  Singleton Instance
     {
         get
         {
             lock  (padlock)
             {
                 if  (instance ==  null )
                 {
                     instance =  new  Singleton();
                 }
                 return  instance;
             }
         }
     }
}

加锁之后,多线程的操作也变的同步了,同一时间只能有获得锁的那个线程才能创建对象。这保证了对象的唯一,但是这个会损耗性能。

因为每次get的时候,都会加锁,因此可以把代码修改一下,如果对象已经存在了,就不需要加锁来创建对象。代码修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public  sealed  class  Singleton
{
     private  static  volatile  Singleton instance =  null ;
     private  static  readonly  object  padlock =  new  object ();
 
     Singleton()
     {
     }
 
     public  static  Singleton Instance
     {
         get
         {
             if  (instance ==  null )
             {
                 lock  (padlock)
                 {
                     if  (instance ==  null )
                     {
                         instance =  new  Singleton();
                     }
                 }
             }
             return  instance;
         }
     }
}

仔细看一下,就会发现,上述代码中,有2次检测instance == null,因此也成为双重检测。注意,由于Java中内存模型的问题,上述双重检测代码,对Java不一定有效。

在C#代码中,考虑下面的场景,有2个线程A,B

1.A线程进入get

2.A线程检测到instance==null

3.A线程获得锁

4.A线程实例化一个对象

5.B线程进入get

6.B线程检测是否instance==null

问题出现在第4,5,6步骤。

当第4步执行的时候,很有可能出现这种情况,CLR为Singleton对象在托管堆上分配了空间,并且让instance指向了这个空间,然后再去调用Singleton的构造函数。而漏洞就在这里,线程B可能未等到线程A运行完Singleton的构造函数,就进入get检测instance!=null,认为对象不是null,然后就直接返回instance,而此时,instance指向的值还在初始化呢,这就可能导致线程B得到的对象是没完全初始化成功的,可能引起代码错误。当然,这种错误的可能性非常少见,但还是会有一定的概率。

因此,上述代码中instance变量加了一个关键字volatile,加它的作用,是为了访问这个instance的时候,确保instance分配了空间并且初始化完成了(volatile确保该字段在任何时间呈现的都是最新的值)。

如果不使用volatile关键字,也可以将instance = new Singleton();语句替换成Interlocked.Exchange(ref instance,new Singleton())。

在C#中,对于实现单例模式,更为推崇的方法是使用静态变量初始化。

有些设计模式的书中,避免使用静态初始化的原因之一是C++ 规范在静态变量的初始化顺序方面留下了一些多义性。幸运的是,.NET Framework 通过其变量初始化处理方法解决了这种多义性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public  sealed  class  Singleton
{
    private  static  readonly  Singleton instance =  new  Singleton();
    
    private  Singleton(){}
 
    public  static  Singleton Instance
    {
       get 
       {
          return  instance; 
       }
    }
}

CLR保证了静态字段初始化操作总是线程安全的,无论多少线程同时访问该类,类中的静态字段只可能被初始化一次(每个应用程序域)。

这是因为,类中的静态字段的初始化,是由类的静态构造函数完成的,C#编译器检测到类中有静态字段后,会为该类生产一个静态构造函数(可以从IL代码中看到.cctor方法),也就是说下面代码是等价的:

1
2
3
class  SomeType{
     Static  int  x = 5;
     }

等价于

1
2
3
4
5
6
7
8
class  SomeType
{
     Static  int  x;
     Static SomeType()
     {
         x = 5;
     }
}


因此上述的instance只会初始化一次。保证了单例。这种方法使用了CLR的特性,对于其他语言并不保证,是.NET平台上推崇的一种实现singleton的方式。

上述实现方法有个不足在于不能延迟加载对象,如果Singleton中还有其他静态字段,引用该静态字段的时候,会导致Singleton被创建了。因此,可以在该类中再嵌套一个类来实现延迟加载。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public  sealed  class  Singleton
{
     private  Singleton()
     {
     }
 
     public  static  Singleton Instance {  get  return  Nested.instance; } }
         
     private  class  Nested
     {
         internal  static  readonly  Singleton instance =  new  Singleton();
     }
}

上述代码,只有当访问Instance属性的时候,才会触发Nested类的静态字段,从而初始化一个Singleton对象,因此实现了延迟加载,但是设计比较复杂,不推荐使用。

在.NET4.0中,还有一种更优雅的方法实现延迟加载,即使用Lazy<T>对象。

1
2
3
4
5
6
7
8
9
10
11
public  sealed  class  Singleton
{
     private  static  readonly  Lazy<Singleton> lazy =
         new  Lazy<Singleton>(() =>  new  Singleton());
     
     public  static  Singleton Instance {  get  return  lazy.Value; } }
 
     private  Singleton()
     {
     }
}

可以参考之前总结的文章http://cnn237111.blog.51cto.com/2359144/1213187


参考:

http://csharpindepth.com/Articles/General/Singleton.aspx#unsafe

http://msdn.microsoft.com/en-us/library/ff650316.aspx

http://mcwilling.blog.163.com/blog/static/1950971712013357359564/

NET4.0面向对象编程漫谈基础篇.金旭亮















本文转自cnn23711151CTO博客,原文链接: http://blog.51cto.com/cnn237111/1425949,如需转载请自行联系原作者



相关文章
|
设计模式 C#
C# 机房重构单例模式
C# 机房重构单例模式
54 0
|
设计模式 安全 C#
【设计模式】C#实现单例模式
单例模式:某一个类在系统中只需要有一个实例对象,而且对象是由这个类自行实例化并提供给系统其它地方使用。单例模式属于一种创建型设计模式。
|
设计模式 开发框架 缓存
C# 程序开发:设计模式之单例模式
1、定义:单例模式就是保证在整个应用程序的生命周期中,在任何时刻,被指定的类只有一个实例,并为客户程序提供一个获取该实例的全局访问点。 首先来明确一个问题,那就是在某些情况下,有些对象,我们只需要一个就可以了。
1121 0
|
C#
C#设计模式之一单例模式(Singleton Pattern)【创建型】
原文:C#设计模式之一单例模式(Singleton Pattern)【创建型】 一、引言     看了李建忠老师的讲的设计模式已经有一段时间了(这段时间大概有一年多了),自己还没有写过自己的、有关设计模式的文章。
2210 0
C# 单例模式的五种写法
C# 单例模式的五种写法及优劣分析,见下文: [单例模式及常见写法](http://blog.csdn.net/jiankunking/article/details/50867050) $(function () { $('pre.prettyprint code').each(function
1983 0
|
安全 C#
C#单例模式的三种写法
第一种最简单,但没有考虑线程安全,在多线程时可能会出问题  public class Singleton{    private static Singleton _instance = null;    private Singleton(){}    public static Sing...
830 0