0%

[c#]c#中单例模式的实现

c#中单例模式的实现#

本质上,单例模式是一个只允许创建一个实例,并提供对这个实例简单的访问途径的类。一般而言,单例模式在创建实例时不允许传递任何参数,否则不同参数导致不同的实例创建,就会出现问题!(如果同一个实例可以被同参的不同请求所访问,那么工厂模式会更适合。)只有在第一次被用到的时候才会被创建。在C#中有实现单例模式有很多种方法。我展示如下三个版本:从线程不安全的到简单线程安全的、再到简洁高效的版本。所有的这些实现都有以下四个特征:

  1. 只有一个构造函数,而且是私有的,不带参数的。这是为了防止其他类对其实例化,同时也防止了子类化--如果一个单例能被子类化 一次,就能被子类化两次,而如果每个子类可以创建一个实例,这与模式本身又产生了冲突。如果你(遇到这样的情况):只有在运行期才能知道实际的类型,因此需要一个父类的单例,可以使用工厂模式。
  2. 类是密封的(即不可以被继承的类)。这并不是必须的,严格的说,即如上一点所说的原因,可以提高JIT(Just-In-Time , 运行时编译执行的技术)的效率。
  3. 一个静态变量用来保存单例的引用
  4. 一个用以访问单例引用的公用静态方法
版本1-非线程安全#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

// 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,于是都创建了实例,这又违背了单例模式。无法确保只能让一个线程使对象实例化

版本2- 简单的线程安全#
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
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;
}
}
}
}

这个实现是线程安全的。线程首先对共用的对象进行锁定,然后判断实例是否在之前已经创建。不幸的是,由于在每次访问的时候都要进行锁定,所以影响了性能。(这对于多 线程并发的高性能要求的应用显得尤为重要)。对那些可被其他类访问的对象进行锁定或对类型进行锁定会导致性能问题甚至引起死锁

版本3- 用双重检测机制的错误版本(没用公用只读对象)#
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
//sealed关键字,表示不能被继承
public sealed class Singleton
{
static Singleton instance=null;
Singleton()
{
}
public static Singleton Instance
{
get
{
//先判断是否存在单例
if (instance==null)
{
lock (instance)
{
if (instance==null)
{
instance = new Singleton();
}
}
}
return instance;
}
}
}

这个是错误的,在c#中,lock空指针会报错。

版本4- 用双重检测机制的线程安全#
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
//sealed关键字,表示不能被继承
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;
}
}
}

这个实现是线程安全的。线程先判断是否存在单例,不存在实例才对共用的对象进行锁定,然后判断实例是否在之前已经创建。看似看似没有问题,但却会引发另一个问题,这个问题由指令重排序引起。指令重排序是为了优化指令,提高程序运行效率。指令重排序包括编译器重排序和运行时重排序。例如 instance = new Singleton() 可分解为如下伪代码:

排序前

1
2
3
memory = allocate();   //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance = memory; //3:设置instance指向刚分配的内存地址

排序后

1
2
3
4
memory = allocate();   //1:分配对象的内存空间
instance = memory; //3:设置instance指向刚分配的内存地址
//注意,此时对象还没有被初始化!
ctorInstance(memory); //2:初始化对象
由于并行执行,则线程A执行了instance = memory然后线程B检查到instance不为null,则会使用未经实例化完全的对象进行操作,引发错误。

版本5- 用双重检测机制的线程安全(加入volatile)#

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
28
29
// Bad code! Do not use!

public sealed class Singleton
{
private volatile static Singleton instance = null;//volatile 可以禁止
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;
}
}
}

volatile表明属性将被多个线程同时访问,告知编译器不要按照单线程访问的方式去优化该字段,线程会监听字段变更,但是不保证字段访问总是顺序执行

版本6- 线程安全,避免实例创建和引用赋值混乱#

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
28
29
30
31
public sealed class Singleton
{
private static Singleton instance = null;//volatile 可以禁止
private static readonly object padlock = new object();

Singleton()
{
}

public static Singleton Instance
{
get
{
if (instance == null)
{
lock (padlock)
{
if (instance == null)
{
var tmp= new Singleton();
// ensures that the instance is well initialized,
// and only then, it assigns the static variable.
System.Threading.Thread.MemoryBarrier();
instance = tmp;
}
}
}
return instance;
}
}
}

官方文档是说Thread.MemoryBarrier(),保证之前的数据存取优先于MemoryBarrier执行,只有在多CPU下才需要使用

版本7- 不用锁实现线程安全,不全是延时初始化#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public sealed class Singleton
{
private static readonly Singleton instance = new Singleton();

// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Singleton()
{
}

private Singleton()
{
}

public static Singleton Instance
{
get
{
return instance;
}
}
}

C#中的静态构造函数被指定仅在创建类的实例或引用静态成员时执行,并且每个AppDomain只执行一次,所以是延时初始化,但是当Singleton有多个静态属性,且其他的属性被访问时,实例也会被初始化,所以不是完全的延时初始化方式

版本8- 不用锁实现线程安全,不全是延时初始化#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public sealed class Singleton
{
private Singleton()
{
}

public static Singleton Instance { get { return Nested.instance; } }

private class Nested
{
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Nested()
{
}

internal static readonly Singleton instance = new Singleton();
}
}

只有当Nested类的第一个引用调用时,Singleton才会被实例化,也就是调用Instance,是完全的延时初始化

版本9- 线程安全, .NET 4之后支持#

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()
{
}
}