意图

单例模式是一种创建型模式,它能确保一个类只有一个实例,并提供一个访问该实例的全局节点。

问题

  1. 单例模式同时解决了两个问题, 所以违反了单一职责原则
    保证一个类只有一个实例。 为什么会有人想要控制一个类所拥有的实例数量? 最常见的原因是控制某些共享资源(例如数据库或文件)的访问权限。
    它的运作方式是这样的: 如果你创建了一个对象, 同时过一会儿后你决定再创建一个新对象, 此时你会获得之前已创建的对象, 而不是一个新对象。
    注意, 普通构造函数无法实现上述行为, 因为构造函数的设计决定了它必须总是返回一个新对象。
    客户端甚至可能没有意识到它们一直都在使用同一个对象

  2. 为该实例提供一个全局访问节点。想想你曾用过的那些存储重要对象的全局变量,它们在使用上十分方便, 但同时也非常不安全,因为任何代码都有可能覆盖掉那些变量的内容,从而引发程序崩溃。
    和全局变量一样, 单例模式也允许在程序的任何地方访问特定对象。 但是它可以保护该实例不被其他代码覆盖。
    还有一点: 你不会希望解决同一个问题的代码分散在程序各处的。 因此更好的方式是将其放在同一个类中, 特别是当其他代码已经依赖这个类时更应该如此。

解决方案

一个类的对象永远只会在当前进程中被创建一次,也就是说构造函数只可能被调用一次,不论有多少线程调用。为什么需要单例,假如这个类是用来操作某个资源的,如果存在多个这个类的实例,这可能在操作这个资源的时候造成破坏,所以只能创建一个实例是很有必要的。

所有单例的实现都包含以下两个相同的步骤:

  1. 将默认构造函数设为私有, 防止其他对象使用单例类的new运算符。
  2. 新建一个静态构建方法作为构造函数。 该函数会 “偷偷” 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象。

如果你的代码能够访问单例类, 那它就能调用单例类的静态方法。 无论何时调用该方法, 它总是会返回相同的对象。

结构

  1. 单例(Singleton)类声明了一个名为get­Instance获取实例的静态方法来返回其所属类的一个相同实例。
    单例的构造函数必须对客户端(Client)代码隐藏。 调用获取实例方法必须是获取单例对象的唯一方式。

实现方式

  1. 在类中添加一个私有静态成员变量用于保存单例实例。
  2. 声明一个公有静态构建方法用于获取单例实例。
  3. 在静态方法中实现”延迟初始化”。该方法会在首次被调用时创建一个新对象,并将其存储在静态成员变量中。此后该方法每次被调用时都返回该实例。
  4. 将类的构造函数设为私有。 类的静态方法仍能调用构造函数, 但是其他对象不能调用。
  5. 检查客户端代码, 将对单例的构造函数的调用替换为对其静态构建方法的调用。

代码演示

一般来说,直接把对象声明为静态即可,程序集在加载过程中进行构造,这个也是线程安全的。但问题是如果此对象一直没有被调用,同时构造函数的开销较大,这个会造成资源浪费。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Singleton
{
private static Singleton instance = new Singleton();

/// <summary>
/// 构造函数声明为私有
/// </summary>
private Singleton()
{
Console.WriteLine("执行构造函数");
}

public static Singleton Get()
{
return instance;
}
}

著名的双检锁法,只在需要时执行构造函数,同时也是线程安全的

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
32
33
class Singleton
{
private static Singleton instance = null;
private static readonly object lockobj = new object();

/// <summary>
/// 构造函数声明为私有
/// </summary>
private Singleton()
{
Console.WriteLine("执行构造函数");
}

/// <summary>
/// 双检锁
/// </summary>
/// <param name="owner"></param>
/// <returns></returns>
public static Singleton Get()
{
if (instance == null)
{
lock (lockobj)
{
if (instance == null)
{
instance = new Singleton();
}
}
}
return instance;
}
}

线程安全单例的完整代码示例:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
using System;
using System.Threading;

namespace Singleton
{
// This Singleton implementation is called "double check lock". It is safe
// in multithreaded environment and provides lazy initialization for the
// Singleton object.
class Singleton
{
private Singleton() { }

private static Singleton _instance;

// We now have a lock object that will be used to synchronize threads
// during first access to the Singleton.
private static readonly object _lock = new object();

public static Singleton GetInstance(string value)
{
// This conditional is needed to prevent threads stumbling over the
// lock once the instance is ready.
if (_instance == null)
{
// Now, imagine that the program has just been launched. Since
// there's no Singleton instance yet, multiple threads can
// simultaneously pass the previous conditional and reach this
// point almost at the same time. The first of them will acquire
// lock and will proceed further, while the rest will wait here.
lock (_lock)
{
// The first thread to acquire the lock, reaches this
// conditional, goes inside and creates the Singleton
// instance. Once it leaves the lock block, a thread that
// might have been waiting for the lock release may then
// enter this section. But since the Singleton field is
// already initialized, the thread won't create a new
// object.
if (_instance == null)
{
_instance = new Singleton();
_instance.Value = value;
}
}
}
return _instance;
}

// We'll use this property to prove that our Singleton really works.
public string Value { get; set; }
}

class Program
{
static void Main(string[] args)
{
// The client code.

Console.WriteLine(
"{0}\n{1}\n\n{2}\n",
"If you see the same value, then singleton was reused (yay!)",
"If you see different values, then 2 singletons were created (booo!!)",
"RESULT:"
);

Thread process1 = new Thread(() =>
{
TestSingleton("FOO");
});
Thread process2 = new Thread(() =>
{
TestSingleton("BAR");
});

process1.Start();
process2.Start();

process1.Join();
process2.Join();
}

public static void TestSingleton(string value)
{
Singleton singleton = Singleton.GetInstance(value);
Console.WriteLine(singleton.Value);
}
}
}

执行结果:

1
2
FOO
FOO

参考原文:单例设计模式