设计模式之一:单例模式(Singleton Pattern)
写这个系列的文章,只为把所学的设计模式再系统的整理一遍。错误和不周到的地方欢迎大家批评。点击这里下载源代码。
什么时候使用单例模式
在程序运行时,某种类型只需要一个实例时,一般采用单例模式。为什么需要一个实例?第一,性能,第二,保持代码简洁,比如程序中通过某个配置类A读取配置文件,如果在每处使用的地方都new A(),才能读取配置项,一个是浪费系统资源(参考.NET垃圾回收机制),再者重复代码太多。
单例模式的实现
实现单例模式,方法非常多,这里我把见过的方式都过一遍,来体会如何在支持并发访问、性能、代码简洁程度等方面不断追求极致。(单击此处下载代码)
实现1:非线程安全
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Threading;
6: using System.Threading.Tasks;
7:
8: namespace SingletonPatternNotTheadSafe
9: {
10: public sealed class Singleton
11: {
12: private static Singleton instance = null;
13:
14: private Singleton()
15: {
16: }
17:
18: public static Singleton Instance
19: {
20: get
21: {
22: if (instance == null)
23: {
24: Thread.Sleep(1000);
25: instance = new Singleton();
26: Console.WriteLine(string.Format(
27: "[{0}]创建Singleton {1}" , Thread.CurrentThread.ManagedThreadId, instance.GetHashCode()));
28: }
29:
30: Console.WriteLine(string.Format(
31: "[{0}]获得Singleton {1}", Thread.CurrentThread.ManagedThreadId, instance.GetHashCode()));
32: return instance;
33: }
34: }
35: }
36: }
为了能够在下面的测试代码中展示上面代码的问题,这里在创建对象前,让线程休息1秒,并且在控制台打印出当前线程ID、对象的hashcode(一般不同对象的hashcode是不一样的,但可能重复)。
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Threading;
6: using System.Threading.Tasks;
7:
8: namespace SingletonPatternNotTheadSafe
9: {
10: class Program
11: {
12: private static void Main(string[] args)
13: {
14: Thread t1 = new Thread(new ThreadStart(Compute));
15:
16: t1.Start();
17:
18: Compute();
19:
20: Console.ReadLine(); // 阻止主线程结束
21: }
22:
23: private static void Compute()
24: {
25: Singleton o1 = Singleton.Instance;
26: }
27: }
28: }
执行结果如下:
分析:
Singleton.Instance的get方法中创建instance并未考虑并发访问的情况,导致可能重复创建Singleton对象。下面的实现方法修复了此问题。
实现2:简单线程安全
要解决上面的问题,最简单的方法就是在创建对象的时候加锁。
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Threading;
6: using System.Threading.Tasks;
7:
8: namespace SingletonSimpleThreadSafe
9: {
10: public sealed class Singleton
11: {
12: private static Singleton instance = null;
13: private static readonly object _lock = new object();
14:
15: private Singleton()
16: {
17: }
18:
19: public static Singleton Instance
20: {
21: get
22: {
23: lock (_lock)
24: {
25: if (instance == null)
26: {
27: Thread.Sleep(1000);
28: instance = new Singleton();
29: Console.WriteLine(string.Format(
30: "[{0}]创建Singleton {1}", Thread.CurrentThread.ManagedThreadId, instance.GetHashCode()));
31: }
32: }
33:
34: Console.WriteLine(string.Format(
35: "[{0}]获得Singleton {1}", Thread.CurrentThread.ManagedThreadId, instance.GetHashCode()));
36: return instance;
37: }
38: }
39: }
40: }
测试代码如下:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Threading;
6: using System.Diagnostics;
7: using System.Threading.Tasks;
8:
9: namespace SingletonSimpleThreadSafe
10: {
11: class Program
12: {
13: private static void Main(string[] args)
14: {
15: SingletonTest();
16: }
17:
18: private static void SingletonTest()
19: {
20: Thread t1 = new Thread(new ThreadStart(Compute));
21:
22: t1.Start();
23:
24: Compute();
25:
26: Console.ReadLine(); // 阻止主线程结束
27: }
28:
29: private static void Compute()
30: {
31: Singleton o1 = Singleton.Instance;
32: }
33: }
34: }
我们再看看执行效果:
创建Singleton只执行一次。但是这种写法性能并不高,每次通过Singleton.Instance获得实例对象都需要判断锁是否别别的线程占用。
这里我们修改一下Singleton,把代码中的Thread.Sleep和Console.Writeline都去掉,这里我重新创建了一个Singleton2 class,两个线程中循环调用100000000次,看一下这么实现的性能:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Threading;
6: using System.Threading.Tasks;
7:
8: namespace SingletonSimpleThreadSafe
9: {
10: public sealed class Singleton2
11: {
12: private static Singleton2 instance = null;
13: private static readonly object _lock = new object();
14:
15: private Singleton2()
16: {
17: }
18:
19: public static Singleton2 Instance
20: {
21: get
22: {
23: lock (_lock)
24: {
25: if (instance == null)
26: {
27: instance = new Singleton2();
28: }
29: }
30:
31: return instance;
32: }
33: }
34: }
35: }
测试代码如下:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Threading;
6: using System.Diagnostics;
7: using System.Threading.Tasks;
8:
9: namespace SingletonSimpleThreadSafe
10: {
11: class Program
12: {
13: private static void Main(string[] args)
14: {
15: Singleton2Test();
16: }
17:
18: private static void Singleton2Test()
19: {
20: Thread t1 = new Thread(new ThreadStart(Compute2));
21:
22: t1.Start();
23:
24: Compute2();
25:
26: Console.ReadLine(); // 阻止主线程结束
27: }
28:
29: private static void Compute2()
30: {
31: Stopwatch sw1 = new Stopwatch();
32:
33: sw1.Start();
34:
35: for (int i = 0; i < 100000000; i++)
36: {
37: Singleton2 instance = Singleton2.Instance;
38: }
39:
40: sw1.Stop();
41:
42: Console.WriteLine(string.Format("[{0}]耗时:{1}毫秒",
43: Thread.CurrentThread.ManagedThreadId,
44: sw1.ElapsedMilliseconds));
45: }
46: }
47: }
执行结果:
我们先不讨论结果,接着往下看看双检锁方式的性能。
实现3:双检锁实现的线程安全
Singleton双检锁实现:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Threading;
6: using System.Threading.Tasks;
7:
8: namespace SingletonDoubleCheckThreadSafe
9: {
10: public sealed class Singleton2
11: {
12: private static Singleton2 instance = null;
13: private static readonly object _lock = new object();
14:
15: private Singleton2()
16: {
17: }
18:
19: public static Singleton2 Instance
20: {
21: get
22: {
23: if (instance == null)
24: {
25: lock (_lock)
26: {
27: if (instance == null)
28: {
29: instance = new Singleton2();
30: }
31: }
32: }
33:
34: return instance;
35: }
36: }
37: }
38: }
测试代码和上面的一样,结果如下:
性能提高了(7571+7465-1410-1412)/ (7571+7465) * 100% = 81.2%。(实际项目中为了减少误差,应该跑多遍测试得到多个结果的平均值和方差,这里为了方便,我只把一次测试结果贴出来。)
双检锁机制在lock外又检查了一次instance是否为null,这样在第一次访问使instance创建后,后面的调用都无需检查lock是否被占用。
一名程序员要了解到这里算基本合格,如果想达到更高的水平,继续往下看。这种方式有什么缺点呢?
- 上面的代码在Java中不能正常工作。这是因为Java的Memory Model实现和.NET不一样,并不保证一定在构造函数执行完成后才返回对象的引用。虽然Java 1.5版本重构了Memory Model,但是双检锁机制在不给instance field加volatile关键字时,依然不能正常工作。
- Microsoft的.net memory model并不是按照标准的ECMA CLI规范实现,而是在标准上做了一些“增强”工作。MS .net CLR memory model中所有的写操作都是VolatileWrite(参考《CLR via C#》第二版的第24章)。所以我们的代码中不加volatile也能在IA64CPU 架构的机器上正常执行。但是如Jeffrey建议,最好还是遵循ECMA标准。
- 实现复杂。
实现4:非懒加载,无锁实现线程安全
.NET中的static变量在class被第一次实例化的时候创建,且保证仅执行一次创建。利用这个特点,可以像如下实现:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Threading.Tasks;
6:
7: namespace SingletonNotUsingLock
8: {
9: public class Singleton
10: {
11: private volatile static Singleton instance = new Singleton();
12:
13: // Explicit static constructor to tell C# compiler
14: // not to mark type as beforefieldinit
15: static Singleton()
16: {
17: Console.WriteLine("execute static constructor");
18: }
19:
20: private Singleton()
21: {
22: Console.WriteLine("execute private constructor");
23: }
24:
25: public static Singleton Instance
26: {
27: get
28: {
29: Console.WriteLine("instance get");
30: return instance;
31: }
32: }
33: }
34: }
上面的代码可以更简化一些,去掉Instance属性,将私有的instance变量改成public的:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Threading.Tasks;
6:
7: namespace SingletonNotUsingLock
8: {
9: public class Singleton2
10: {
11: public volatile static Singleton2 instance = new Singleton2();
12:
13: // Explicit static constructor to tell C# compiler
14: // not to mark type as beforefieldinit
15: static Singleton2()
16: {
17: Console.WriteLine("execute static constructor");
18: }
19:
20: private Singleton2()
21: {
22: Console.WriteLine("execute private constructor");
23: }
24: }
25: }
代码非常简洁。但是为什么有个静态构造函数呢,我们看看下面的测试代码:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Threading.Tasks;
6:
7: namespace SingletonNotUsingLock
8: {
9: class Program
10: {
11: static void Main(string[] args)
12: {
13: Console.WriteLine("begin create singleton");
14:
15: Singleton s1 = Singleton.Instance;
16:
17: Console.WriteLine("after create singleton");
18:
19: Singleton2 s2 = Singleton2.instance;
20:
21: Console.WriteLine("after create singleton2");
22: }
23: }
24: }
执行结果如下:
把静态构造函数去掉后执行结果如下:
这是因为没有静态构造函数的类,编译时会被标记称beforefieldinit,那么,beforefieldinit究竟表示什么样的语义呢?Scott Allen对此进行了详细的解释:beforefieldinit为CLR提供了在任何时候执行.cctor的授权,只要该方法在第一次访问类型的静态字段之前执行即可。
实现5:无锁懒加载
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Threading.Tasks;
6:
7: namespace SingletonNotUsingLockAndLazyLoad
8: {
9: public class Singleton
10: {
11: private Singleton()
12: {
13: Console.WriteLine("execute Singleton private constructor");
14: }
15:
16: public static Singleton Instance
17: {
18:
19: get
20: {
21: Console.WriteLine("execute Singleton.Instance get");
22: return Nested.instance;
23: }
24: }
25:
26: private class Nested
27: {
28: // Explicit static constructor to tell C# compiler
29: // not to mark type as beforefieldinit
30: static Nested()
31: {
32: Console.WriteLine("execute Nested static constructor");
33: }
34:
35: internal static readonly Singleton instance = new Singleton();
36: }
37: }
38: }
实现6:使用.NET 4.0中的Lazy<T>
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Threading.Tasks;
6:
7: namespace SingletonUsingLazyType
8: {
9: public sealed class Singleton
10: {
11: private static readonly Lazy<Singleton> lazy =
12: new Lazy<Singleton>(() => new Singleton());
13:
14: public static Singleton Instance { get { return lazy.Value; } }
15:
16: private Singleton()
17: {
18: }
19: }
20: }
参考:
- Exploring the Singleton Design Pattern
- C#设计模式(7)-Singleton Pattern
- Implementing the Singleton Pattern in C#
- c#静态构造函数
- C# and beforefieldinit
- 《研磨设计模式》
- 关于Type Initializer和 BeforeFieldInit的问题,看看大家能否给出正确的解释
- [你必须知道的.NET]第二十三回:品味细节,深入.NET的类型构造器
设计模式之一:单例模式(Singleton Pattern)的更多相关文章
- 乐在其中设计模式(C#) - 单例模式(Singleton Pattern)
原文:乐在其中设计模式(C#) - 单例模式(Singleton Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 单例模式(Singleton Pattern) 作者:weba ...
- 设计模式之单例模式(Singleton Pattern)
单例模式 单例模式(Singleton Pattern)在java中算是最常用的设计模式之一,主要用于控制控制类实例的数量,防止外部实例化或者修改.单例模式在某些场景下可以提高系统运行效率.实现中的主 ...
- 【设计模式】单例模式 Singleton Pattern
通常我们在写程序的时候会碰到一个类只允许在整个系统中只存在一个实例(Instance) 的情况, 比如说我们想做一计数器,统计某些接口调用的次数,通常我们的数据库连接也是只期望有一个实例.Windo ...
- 二十四种设计模式:单例模式(Singleton Pattern)
单例模式(Singleton Pattern) 介绍保证一个类仅有一个实例,并提供一个访问它的全局访问点. 示例保证一个类仅有一个实例. Singleton using System; using S ...
- 乐在其中设计模式(C#) - 单例模式(Singleton Pattern)【转】
介绍 保证一个类仅有一个实例,并提供一个访问它的全局访问点. 示例 保证一个类仅有一个实例. Singleton using System; using System.Collections.Gene ...
- Java 设计模式(三)-单例模式(Singleton Pattern)
1 概念定义 1.1 定义 确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例. 1.2 类型 创建类模式 1.3 难点 1)多个虚拟机 当系统中的单例类被拷贝运行在多 ...
- python 设计模式之单例模式 Singleton Pattern
#引入 一个类被设计出来,就意味着它具有某种行为(方法),属性(成员变量).一般情况下,当我们想使用这个类时,会使用new 关键字,这时候jvm会帮我们构造一个该类的实例.这么做会比较耗费资源. 如果 ...
- 【UE4 设计模式】单例模式 Singleton Pattern
概述 描述 保证一个类只有一个实例 提供一个访问该实例的全局节点,可以视为一个全局变量 仅在首次请求单例对象时对其进行初始化. 套路 将默认构造函数设为私有, 防止其他对象使用单例类的 new运算符. ...
- 浅谈设计模式--单例模式(Singleton Pattern)
题外话:好久没写blog,做知识归纳整理了.本来设计模式就是个坑,各种文章也写烂了.不过,不是自己写的东西,缺少点知识的存在感.目前还没做到光看即能记住,得写.所以准备跳入设计模式这个大坑. 开篇先贡 ...
- 设计模式系列之单例模式(Singleton Pattern)——确保对象的唯一性
模式概述 模式定义 模式结构图 饿汉式单例与懒汉式单例 饿汉式单例 懒汉式单例 模式应用 模式在JDK中的应用 模式在开源项目中的应用 模式总结 主要优点 适用场景 说明:设计模式系列文章是读刘伟所著 ...
随机推荐
- 批量改名的多种方法stu_3_finished.jpg 去掉finished,stu_{1..20}_finished.jpg
方法一:rename修改文件名 rename "finished" "" *.jpg [root@ob1 scripts]# rename "fini ...
- SSI——服务器端嵌入
服务器端嵌入:Server Side Include,是一种类似于ASP的基于服务器的网页制作技术.大多数(尤其是基于Unix平台)的WEB服务器如Netscape Enterprise Server ...
- 转载------让IE6 IE7 IE8 IE9 IE10 IE11支持Bootstrap的解决方法
本文是转载及收藏 让IE6 IE7 IE8 IE9 IE10 IE11支持Bootstrap的解决方法 最近做一个Web网站,之前一直觉得bootstrap非常好,这次使用了bootstrap3,在c ...
- SQL Server 自动重建出现碎片的索引
1.索引碎片的产生? 由于在表里大量的插入.修改.删除操作而使索引页分裂.如果索引有了高的碎片,有两种情况,一种情况是扫描索引需要花费很多的时间,另一种情况是在查询的时候索引根本不使用索引,都会导致性 ...
- Linux Tcl和Expect的安装
一.先安装Tcl 1.下载:tcl版本 8.4.19 http://sourceforge.net/projects/tcl/files/Tcl/8.4.19/tcl8.4.19-src.tar.gz ...
- matlab下使用svmlib工具包
虽然网络上已经有了很多的类似的东西了吧.但是呢,我自己还是要写一写的. 安装: 对于 libsvm工具包,我们可以去官方网站下载,网址为:http://www.csie.ntu.edu.tw/~cjl ...
- hdu 1281 棋盘游戏 (二分匹配)
//是象棋里的车 符合二分匹配 # include<stdio.h> # include<algorithm> # include<string.h> using ...
- HDU 1556 Color the ball 树状数组 题解
Problem Description N个气球排成一排,从左到右依次编号为1,2,3....N.每次给定2个整数a b(a <= b),lele便为骑上他的"小飞鸽"牌电动 ...
- 转载:QTableView中嵌入可视化组件
出处:http://qimo601.iteye.com/blog/1538364 QTableView中嵌入可视化组件方法有四种: 第一种不能之前显示,必须双击/选中后才能显示,不适用. 第二种比较简 ...
- 【Java面试题】36 List、Map、Set三个接口,存取元素时,各有什么特点?
List与Set都是单列元素的集合,它们有一个功共同的父接口Collection. Set里面不允许有重复的元素, 存元素:add方法有一个boolean的返回值,当集合中没有某个元素,此时add方法 ...