http://www.web008.net

设计模式

今天我们来探索一下Singleton设计模式的实现及应用场景。

单 例模式是广为流传的设计模式中的一种。本质上,单例模式是一个只允许创建一个实例,并提供对这个实例简单的访问途径的类。一般而言,单例模式在创建实例时 不允许传递任何参数-否则不同参数导致不同的实例创建,就会出现问题!(如果同一个实例可以被同参的不同请求所访问,那么工厂模式会更适合。)这篇文章只 针对无参创建的请求进行讨论。典型的,单例模式的应用往往是延后创建的(created lazily)---只有在第一次被用到的时候才会被创建。在C#中有实现单例模式有很多种方法。我将在这里一一展现给大家,从最常见的、线程不安全的到 延后创建的、线程安全的、再到简洁高效的版本。注意在下面的代码中,我忽略了所有私有域,因为私有域是默认的类的成员。In many other languages such as Java, there is a different default, and private should be used.

Singleton模式属于Creational Type(创建型)设计模式的一种。该模式一般用于确保在应用中仅创建一个某类的instance,在应用中的各个地方对该类的实例对象的引用均指向同一instacne。

所有的这些实现都有以下四个特征:

Singleton模式的优势:它提供了对资源的concurrent(一致)访问,避免了创建多个实例对象,浪费内存空间。

1.只有一个构造函数,而且是私有的,不带参数的。这是为了防止其他类对其实例化(这和模式本身有冲突)。同时也防止了子类化--如果一个单例能被子类化 一次,就能被子类化两次,而如果每个子类可以创建一个实例,这与模式本身又产生了冲突。如果你(遇到这样的情况):只有在运行期才能知道实际的类型,因此 你需要一个父类的单例,你可以使用工厂模式。

Singleton模式实现准则:

2.类是密封的。这并不是必须的,严格的说,即如上一点所说的原因,可以提高JIT(Just-In-Time , 运行时编译执行的技术)的效率。

1、确保仅有一个类的实例对象

3.一个静态变量用来保存单例的引用。

2、提供对唯一实例对象的全局访问,该规则又可细分为3条规则。

4.一个用以访问单例引用的公用静态方法。

  • 声明该类的所有构造器为private
  • 提供返回实例对象的引用的静态方法
  • 实例对象被存储为类的private static字段

注意:所有这些实现都用到一个公用静态属性Instance,作为访问实例的方法。当然都可以替换为方法,这对线程安全和性能都没有影响。

下面通过例子实现Singleton模式,在具体实现时,我们首先实现不使用Singleton模式的代码,然后一步一步地将其改造成Singleton模式。

版本1-非线程安全

 1 namespace Singleton
 2 {
 3     class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             Singleton s1 = new Singleton();
 8             s1.PrintMessage("this is first message");
 9 
10             Singleton s2 = new Singleton();
11             s2.PrintMessage("this is second message");
12 
13             Console.ReadKey();
14         }
15     }
16     class Singleton
17     {
18         private static int counter = 0;
19         public Singleton()
20         {
21             counter++;
22             Console.WriteLine("这是第"+counter.ToString()+"次调用构造函数");
23         }
24         public void PrintMessage(String str)
25         {
26             Console.WriteLine(str);
27         }
28     }
29 }

// Bad code! Do not use!
public sealed class Singleton
{
static Singleton instance=null;
Singleton()
{
}
public static Singleton Instance
{
get
{
if (instance==null)
{
instance = new Singleton();
}
return instance;
}
}
}

运行结果如下图:

如 上提示,上面的代码是非线程安全的。两个线程可能同时判断“if (instance==null)”,发现为TRUE,于是都创建了实例,这又违背了单例模式。注意:事实上在表达式反应前实例已经被创建了,内存模型并 不保证新的实例的值被其他的线程看到,除非对应的内存屏障已经通过了。(CPU越过内存屏障后,将刷新自已对存储器的缓冲状态,这样其他线程才能同步自己 的copy)

美高梅163888 1

版本2 - 简单的线程安全

 

public sealed class Singleton
{
static Singleton instance=null;
static readonly object padlock = new object();
Singleton()
{
}
public static Singleton Instance
{
get
{
lock (padlock)
{
if (instance==null)
{
instance = new Singleton();
}
return instance;
}
}
}
}

  可以看出,在不使用Singleton模式时,每次使用类Singleton的PrintMessage方法时,都需要创建一个新的实例对象,这造成了极大的浪费。下面我们按照上面所说的准则将它改造成使用Singleton模式。

这 个实现是线程安全的。线程首先对共用的对象进行锁定,然后判断实例是否在之前已经创建。这里要小心内存屏障问题(在锁定的时候确保所有的读操作发生在所获 得之后,在解锁的时候确保所有的写操作发生在锁释放之前),确保只有一个线程创建了实例(因为在同一时刻只有一个线程可以在执行那段代码--到了第二个线 程进入的时候,第一个线程已经完成了实例的创建,这样表达式返回false)。不幸的是,由于在每次访问的时候都要进行锁定,所以影响了性能。(这对于多 线程并发的高性能要求的应用显得尤为重要)。注意有些该版本的实现对typeof(Singleton)进行锁定,但我是对类的私有静态变量进行锁定。对 那些可被其他类访问的对象进行锁定或对类型进行锁定会导致性能问题甚至引起死锁。这是一个地雷-任何地方都有可能发生,只有对本就为锁定而创建的对象进行 锁定或是那些因为某些目的而被锁定的文档才是安全的。通常这些对象都是私有的。这样有助于写出线程安全的程序。

 1 namespace Singleton
 2 {
 3     class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             Singleton s1 = Singleton.getInstance;
 8             s1.PrintMessage("this is first message");
 9 
10             Singleton s2 = Singleton.getInstance;
11             s2.PrintMessage("this is second message");
12 
13             Console.ReadKey();
14         }
15     }
16     public sealed class Singleton
17     {
18         private static int counter = 0;
19         private static Singleton instance=null;
20 
21         public static Singleton getInstance
22         {
23             get
24             {
25                 if (instance == null)
26                     instance = new Singleton();
27                 return instance;
28             }
29         }
30         private Singleton()
31         {
32             counter++;
33             Console.WriteLine("这是第"+counter.ToString()+"次调用构造函数");
34         }
35         public void PrintMessage(String str)
36         {
37             Console.WriteLine(str);
38         }
39     }
40 }

版本3 -- 用双重检测机制的线程安全

运行结果如下图:

// Bad code! Do not use!
public sealed class Singleton
{
static Singleton instance=null;
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;
}
}
}

美高梅163888 2美高梅163888, 

 

  另外,需要注意的是,不要忘了在Singleton类的声明中添加sealed关键字,它确保了Singleton类不可被继承。当Singleton类可被继承且继承类位于Singleton基类外部时,由于Singleton基类的构造函数声明为private,此时运行程序会产生Singleton.Singleton()受保护级别限制不可访问的编译错误;当Singleton类可被继承且继承类位于Singleton基类内部构成嵌套类时,这时在main()方法中就可以创建多个继承类的instance,这就违背了Singleton模式的设计初衷:仅能创建该类型的一个instance。

最近在学设计模式,学到创建型模式的时候,碰到单例模式(或叫单件模式),现在整理一下笔记。

  最后,上述代码仅在单线程环境中运行良好,在下篇中介绍多线程环境中的实现,及Double-checked locking和lazy initialization的相关概念。

  在《Design Patterns:Elements of Resuable Object-Oriented Software》中的定义是:Ensure a class only has one instance,and provide a global point of access to。它的主要特点不是根据客户程序调用生成一个新的实例,而是控制某个类型的实例数量-唯一一个。(《设计模式-基于C#的工程化实现及扩展》,王翔)。也就是说,单例模式就是保证在整个应用程序的生命周期中,在任何时刻,被指定的类只有一个实例,并为客户程序提供一个获取该实例的全局访问点。

  一、经典模式:

public class Singleton
{
private static Singleton instance;

private Singleton()
{

}

public static Singleton GetInstance()
{
if(instance==null)
{
instance=new Singleton();
}
return instance;
}
}

郑重声明:本文版权归美高梅163888所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。