设计模式-05.SINGLETON(单件)— 对象创建型模式

1 意图

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

2 别名

3 动机

让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建(通过截取创建新对象的请求),并且它可以提供一个访问该实例的方法。这就是 Singleton 模式。

4 适用性

  • 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
  • 当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。

5 结构

图一

6 参与者

  • Singleton
    • 定义一个 Instance 操作,允许客户访问它的唯一实例。 Instance 是一个类操作。
    • 可能负责创建它自己的唯一实例。

7 协作

客户只能通过 Singleton 的 Instance 操作访问一个 Singleton 的实例。

8 效果

  • 对唯一实例的受控访问
  • 缩小名空间
  • 允许对操作和表示的精化
  • 允许可变数目的实例
  • 比类操作更灵活

9 实现

  • 保证一个唯一的实例
  • 创建 Singleton 类的子类

10 代码示例

  • 第一种:线程不安全,不建议使用

建议:使用sealed关键字标识类不可继承,单例模式的类不应该继承,否则每个子类都可创建实例,违反唯一实例原则。
备注:只适用于单线程,多线程由于未加锁,导致各个线程都会创建一个实例,违反单例模式的基本原则。
As hinted at before, the above is not thread-safe. Two different threads could both have evaluated the test if (instance==null) and found it to be true, then both create instances, which violates the singleton pattern.
Note that in fact the instance may already have been created before the expression is evaluated, but the memory model doesn’t guarantee that the new value of instance will be seen by other threads unless suitable memory barriers have been passed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public sealed class Sington1
{
private static Sington1 instance = null;

private Sington1()
{

}

public static Sington1 Instance
{
get
{
if (instance == null)
instance = new Sington1();

return instance;
}
}
}

  • 第二种:简单版线程安全

备注:该版本代码对于每个线程都会对线程辅助对象locker加锁之后再判断实例是否存在,对于这个操作完全没有必要的,因为当第一个线程创建了该类的实例之后,后面的线程此时只需要直接判断(uniqueInstance==null)为假,此时完全没必要对线程辅助对象加锁之后再去判断,所以上面的实现方式增加了额外的开销,损失了性能。
This implementation is thread-safe. The thread takes out a lock on a shared object, and then checks whether or not the instance has been created before creating the instance. This takes care of the memory barrier issue (as locking makes sure that all reads occur logically after the lock acquire, and unlocking makes sure that all writes occur logically before the lock release) and ensures that only one thread will create an instance (as only one thread can be in that part of the code at a time - by the time the second thread enters it,the first thread will have created the instance, so the expression will evaluate to false). Unfortunately, performance suffers as a lock is acquired every
time the instance is requested.
Note that instead of locking on typeof(Singleton) as some versions of this implementation do, I lock on the value of a static variable which is private to the class. Locking on objects which other classes can access and lock on(such as the type) risks performance issues and even deadlocks.This is a general style preference of mine - wherever possible,only lock on objects specifically created for the purpose of locking, or which document that they are to be locked on for specific purposes (e.g. for waiting/pulsing a queue). Usually such objects should be private to the class they are used in. This helps to make writing thread-safe applications significantly easier.

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
public sealed class Sington2
{
private static Sington2 instance = null;
// 定义一个标识确保线程同步
private static readonly object lockObj = null;

private Sington2()
{

}

public static Sington2 Instance
{
get
{
// 当第一个线程运行到这里时,此时会对locker对象 "加锁",
// 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁
// lock语句运行完之后(即线程运行完之后)会对该对象"解锁"
lock (lockObj)
{
if (instance == null)
instance = new Sington2();

return instance;
}
}
}
}

  • 第三种:线程安全、双重锁定

备注:该版本代码为了改进上面实现方式的缺陷,我们只需要在lock语句前面加一句判断就可以避免锁所增加的额外开销,这种实现方式我们就叫它 “双重锁定”。
This implementation attempts to be thread-safe without the necessity of taking out a lock every time. Unfortunately, there are four downsides to the pattern:
1.It doesn’t work in Java. This may seem an odd thing to comment on, but it’s worth knowing if you ever need the singleton pattern in Java, and c# programmers may well also be Java programmers. The Java memory model doesn’t ensure that the constructor completes before the reference to the new object is assigned to instance. The Java memory model underwent a reworking for version 1.5, but double-check locking is still broken after this without a volatile variable (as in c#).
2.Without any memory barriers, it’s broken in the ECMA CLI specification too. It’s possible that under the.NET 2.0 memory model(which is stronger than the ECMA spec) it’s safe, but I’d rather not rely on those stronger semantics, especially if there’s any doubt as to the safety. Making the instance variable volatile can make it work, as would explicit memory barrier calls, although in the latter case even experts can’t agree exactly which barriers are required.I tend to try to avoid situations where experts don’t agree what’s right and what’s wrong!
3.It’s easy to get wrong. The pattern needs to be pretty much exactly as above - any significant changes are likely to impact either performance or correctness.
4.It still doesn’t perform as well as the later implementations.

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
public sealed class Sington3
{
private static Sington3 instance = null;
// 定义一个标识确保线程同步
private static readonly object lockObj = null;

private Sington3()
{

}

public static Sington3 Instance
{
get
{
// 当第一个线程运行到这里时,此时会对locker对象 "加锁",
// 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁
// lock语句运行完之后(即线程运行完之后)会对该对象"解锁"
// 双重锁定只需要一句判断就可以了
if (instance == null)
{
lock (lockObj)
{
if (instance == null)
instance = new Sington3();
}
}

return instance;
}
}
}

  • 第四种:线程安全、不使用锁、静态初始化

备注:静态初始化的方式是在自己的字段被引用时才会实例化,添加了静态构造函数当静态字段被引用时才进行初始化,因此即便很多线程试图引用_instance,
也需要等静态构造函数执行完并把静态成员_instance实例化之后可以使用。
As you can see, this is really is extremely simple - but why is it thread-safe and how lazy is it? Well, static constructors in c# are specified to execute only when an instance of the class is created or a static member is referenced, and to execute only once per AppDomain. Given that this check for the type being newly constructed needs to be executed whatever else happens, it will be faster than adding extra checking as in the previous examples. There are a couple of wrinkles, however:
1.It’s not as lazy as the other implementations. In particular, if you have static members other than Instance, the first reference to those members will involve creating the instance. This is corrected in the next implementation.
2.There are complications if one static constructor invokes another which invokes the first again.Look in the.NET specifications (currently section 9.5.3 of partition II) for more details about the exact nature of type initializers - they’re unlikely to bite you, but it’s worth being aware of the consequences of static constructors which refer to each other in a cycle.
3.The laziness of type initializers is only guaranteed by.NET when the type isn’t marked with a special flag called beforefieldinit. Unfortunately, the c# compiler (as provided in the .NET 1.1 runtime, at least) marks all types which don’t have a static constructor(i.e.a block which looks like a constructor but is marked static) as beforefieldinit.I now have an article with more details about this issue.Also note that it affects performance, as discussed near the bottom of the page.
One shortcut you can take with this implementation (and only this one) is to just make instance a public static readonly variable, and get rid of the property entirely.This makes the basic skeleton code absolutely tiny!Many people, however, prefer to have a property in case further action is needed in future, and JIT inlining is likely to make the performance identical. (Note that the static constructor itself is still required if you require laziness.)

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
public sealed class Sington4
{
private static Sington4 instance = null;

/// <summary>
/// 添加一个静态构造函数,用于屏蔽IL将变量属性自动标记beforefieldinit。
/// Explicit static constructor to tell c# compiler not to mark type as beforefieldinit
/// </summary>
static Sington4()
{

}

private Sington4()
{

}

public static Sington4 Instance
{
get
{
if (instance == null)
instance = new Sington4();

return instance;
}
}
}

  • 第五种:线程安全、延迟初始化

备注:把初始化工作放到Nested类中的一个静态成员来完成,这样就实现了延迟初始化。
Here, instantiation is triggered by the first reference to the static member of the nested class, which only occurs in Instance. This means the implementation is fully lazy, but has all the performance benefits of the previous ones. Note that although nested classes have access to the enclosing class’s private members, the reverse is not true, hence the need for instance to be internal here. That doesn’t raise any other problems, though, as the class itself is private. The code is a bit more complicated in order to make the instantiation lazy, however.

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
public sealed class Sington5
{
private Sington5()
{

}

public static Sington5 Instance
{
get
{
return SingtonNested.instance;
}
}

internal class SingtonNested
{
internal static Sington5 instance = new Sington5();

// Explicit static constructor to tell c# compiler not to mark type as beforefieldinit
static SingtonNested()
{

}
}
}

  • 第六种:using .NET 4’s Lazy type

备注:简单和性能良好,而且还提供检查是否已经创建实例的属性IsValueCreated。
If you’re using .NET 4 (or higher), you can use the System.Lazy type to make the laziness really simple. All you need to do is pass a delegate to the constructor which calls the Singleton constructor - which is done most easily with a lambda expression.It’s simple and performs well. It also allows you to check whether or not the instance has been created yet with the IsValueCreated property, if you need that.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public sealed class Sington6
{
private static readonly Lazy<Sington6> lazy = new Lazy<Sington6>(() => new Sington6(), true);

private Sington6()
{

}

public static Sington6 Instance
{
get
{
return lazy.Value;
}
}
}

  • 总结

    There are various different ways of implementing the singleton pattern in C#. A reader has written to me detailing a way he has encapsulated the synchronization aspect, which while I acknowledge may be useful in a few very particular situations (specifically where you want very high performance, and the ability to determine whether or not the singleton has been created, and full laziness regardless of other static members being called). I don’t personally see that situation coming up often enough to merit going further with on this page, but please mail me if you’re in that situation.

    My personal preference is for solution 4: the only time I would normally go away from it is if I needed to be able to call other static methods without triggering initialization, or if I needed to know whether or not the singleton has already been instantiated. I don’t remember the last time I was in that situation, assuming I even have. In that case, I’d probably go for solution 2, which is still nice and easy to get right.

    Solution 5 is elegant, but trickier than 2 or 4, and as I said above, the benefits it provides seem to only be rarely useful. Solution 6 is a simpler way to achieve laziness, if you’re using .NET 4. It also has the advantage that it’s obviously lazy. I currently tend to still use solution 4, simply through habit - but if I were working with inexperienced developers I’d quite possibly go for solution 6 to start with as an easy and universally applicable pattern.

    (I wouldn’t use solution 1 because it’s broken, and I wouldn’t use solution 3 because it has no benefits over 5.)

11 已知应用

  • 日志

12 相关模式

很多模式可以使用 Singleton 模式实现。参见 AbstractFactory、Builder,和Prototype。

坚持原创技术分享,您的支持将鼓励我继续创作!