写这个系列的文章,只为把所学的设计模式再系统的整理一遍。错误和不周到的地方欢迎大家批评。点击这里下载源代码。

什么时候使用单例模式

在程序运行时,某种类型只需要一个实例时,一般采用单例模式。为什么需要一个实例?第一,性能,第二,保持代码简洁,比如程序中通过某个配置类A读取配置文件,如果在每处使用的地方都new A(),才能读取配置项,一个是浪费系统资源(参考.NET垃圾回收机制),再者重复代码太多。

单例模式的实现

实现单例模式,方法非常多,这里我把见过的方式都过一遍,来体会如何在支持并发访问、性能、代码简洁程度等方面不断追求极致。(单击此处下载代码)

实现1:非线程安全

  1. 1: using System;

  1. 2: using System.Collections.Generic;

  1. 3: using System.Linq;

  1. 4: using System.Text;

  1. 5: using System.Threading;

  1. 6: using System.Threading.Tasks;

  1. 7: 

  1. 8: namespace SingletonPatternNotTheadSafe

  1. 9: {

  1. 10: public sealed class Singleton

  1. 11: {

  1. 12: private static Singleton instance = null;

  1. 13: 

  1. 14: private Singleton()

  1. 15: {

  1. 16: }

  1. 17: 

  1. 18: public static Singleton Instance

  1. 19: {

  1. 20: get

  1. 21: {

  1. 22: if (instance == null)

  1. 23: {

  1. 24: Thread.Sleep(1000);

  1. 25: instance = new Singleton();

  1. 26: Console.WriteLine(string.Format(

  1. 27: "[{0}]创建Singleton {1}" , Thread.CurrentThread.ManagedThreadId, instance.GetHashCode()));

  1. 28: }

  1. 29: 

  1. 30: Console.WriteLine(string.Format(

  1. 31: "[{0}]获得Singleton {1}", Thread.CurrentThread.ManagedThreadId, instance.GetHashCode()));

  1. 32: return instance;

  1. 33: }

  1. 34: }

  1. 35: }

  1. 36: }

为了能够在下面的测试代码中展示上面代码的问题,这里在创建对象前,让线程休息1秒,并且在控制台打印出当前线程ID、对象的hashcode(一般不同对象的hashcode是不一样的,但可能重复)。

  1. 1: using System;

  1. 2: using System.Collections.Generic;

  1. 3: using System.Linq;

  1. 4: using System.Text;

  1. 5: using System.Threading;

  1. 6: using System.Threading.Tasks;

  1. 7: 

  1. 8: namespace SingletonPatternNotTheadSafe

  1. 9: {

  1. 10: class Program

  1. 11: {

  1. 12: private static void Main(string[] args)

  1. 13: {

  1. 14: Thread t1 = new Thread(new ThreadStart(Compute));

  1. 15: 

  1. 16: t1.Start();

  1. 17: 

  1. 18: Compute();

  1. 19: 

  1. 20: Console.ReadLine(); // 阻止主线程结束

  1. 21: }

  1. 22: 

  1. 23: private static void Compute()

  1. 24: {

  1. 25: Singleton o1 = Singleton.Instance;

  1. 26: }

  1. 27: }

  1. 28: }

执行结果如下:

分析:

Singleton.Instance的get方法中创建instance并未考虑并发访问的情况,导致可能重复创建Singleton对象。下面的实现方法修复了此问题。

实现2:简单线程安全

要解决上面的问题,最简单的方法就是在创建对象的时候加锁。

  1. 1: using System;

  1. 2: using System.Collections.Generic;

  1. 3: using System.Linq;

  1. 4: using System.Text;

  1. 5: using System.Threading;

  1. 6: using System.Threading.Tasks;

  1. 7: 

  1. 8: namespace SingletonSimpleThreadSafe

  1. 9: {

  1. 10: public sealed class Singleton

  1. 11: {

  1. 12: private static Singleton instance = null;

  1. 13: private static readonly object _lock = new object();

  1. 14: 

  1. 15: private Singleton()

  1. 16: {

  1. 17: }

  1. 18: 

  1. 19: public static Singleton Instance

  1. 20: {

  1. 21: get

  1. 22: {

  1. 23: lock (_lock)

  1. 24: {

  1. 25: if (instance == null)

  1. 26: {

  1. 27: Thread.Sleep(1000);

  1. 28: instance = new Singleton();

  1. 29: Console.WriteLine(string.Format(

  1. 30: "[{0}]创建Singleton {1}", Thread.CurrentThread.ManagedThreadId, instance.GetHashCode()));

  1. 31: }

  1. 32: }

  1. 33: 

  1. 34: Console.WriteLine(string.Format(

  1. 35: "[{0}]获得Singleton {1}", Thread.CurrentThread.ManagedThreadId, instance.GetHashCode()));

  1. 36: return instance;

  1. 37: }

  1. 38: }

  1. 39: }

  1. 40: }

测试代码如下:

  1. 1: using System;

  1. 2: using System.Collections.Generic;

  1. 3: using System.Linq;

  1. 4: using System.Text;

  1. 5: using System.Threading;

  1. 6: using System.Diagnostics;

  1. 7: using System.Threading.Tasks;

  1. 8: 

  1. 9: namespace SingletonSimpleThreadSafe

  1. 10: {

  1. 11: class Program

  1. 12: {

  1. 13: private static void Main(string[] args)

  1. 14: {

  1. 15: SingletonTest();

  1. 16: }

  1. 17: 

  1. 18: private static void SingletonTest()

  1. 19: {

  1. 20: Thread t1 = new Thread(new ThreadStart(Compute));

  1. 21: 

  1. 22: t1.Start();

  1. 23: 

  1. 24: Compute();

  1. 25: 

  1. 26: Console.ReadLine(); // 阻止主线程结束

  1. 27: }

  1. 28: 

  1. 29: private static void Compute()

  1. 30: {

  1. 31: Singleton o1 = Singleton.Instance;

  1. 32: }

  1. 33: }

  1. 34: }

我们再看看执行效果:

创建Singleton只执行一次。但是这种写法性能并不高,每次通过Singleton.Instance获得实例对象都需要判断锁是否别别的线程占用。

这里我们修改一下Singleton,把代码中的Thread.Sleep和Console.Writeline都去掉,这里我重新创建了一个Singleton2 class,两个线程中循环调用100000000次,看一下这么实现的性能:

  1. 1: using System;

  1. 2: using System.Collections.Generic;

  1. 3: using System.Linq;

  1. 4: using System.Text;

  1. 5: using System.Threading;

  1. 6: using System.Threading.Tasks;

  1. 7: 

  1. 8: namespace SingletonSimpleThreadSafe

  1. 9: {

  1. 10: public sealed class Singleton2

  1. 11: {

  1. 12: private static Singleton2 instance = null;

  1. 13: private static readonly object _lock = new object();

  1. 14: 

  1. 15: private Singleton2()

  1. 16: {

  1. 17: }

  1. 18: 

  1. 19: public static Singleton2 Instance

  1. 20: {

  1. 21: get

  1. 22: {

  1. 23: lock (_lock)

  1. 24: {

  1. 25: if (instance == null)

  1. 26: {

  1. 27: instance = new Singleton2();

  1. 28: }

  1. 29: }

  1. 30: 

  1. 31: return instance;

  1. 32: }

  1. 33: }

  1. 34: }

  1. 35: }

测试代码如下:

  1. 1: using System;

  1. 2: using System.Collections.Generic;

  1. 3: using System.Linq;

  1. 4: using System.Text;

  1. 5: using System.Threading;

  1. 6: using System.Diagnostics;

  1. 7: using System.Threading.Tasks;

  1. 8: 

  1. 9: namespace SingletonSimpleThreadSafe

  1. 10: {

  1. 11: class Program

  1. 12: {

  1. 13: private static void Main(string[] args)

  1. 14: {

  1. 15: Singleton2Test();

  1. 16: }

  1. 17: 

  1. 18: private static void Singleton2Test()

  1. 19: {

  1. 20: Thread t1 = new Thread(new ThreadStart(Compute2));

  1. 21: 

  1. 22: t1.Start();

  1. 23: 

  1. 24: Compute2();

  1. 25: 

  1. 26: Console.ReadLine(); // 阻止主线程结束

  1. 27: }

  1. 28: 

  1. 29: private static void Compute2()

  1. 30: {

  1. 31: Stopwatch sw1 = new Stopwatch();

  1. 32: 

  1. 33: sw1.Start();

  1. 34: 

  1. 35: for (int i = 0; i < 100000000; i++)

  1. 36: {

  1. 37: Singleton2 instance = Singleton2.Instance;

  1. 38: }

  1. 39: 

  1. 40: sw1.Stop();

  1. 41: 

  1. 42: Console.WriteLine(string.Format("[{0}]耗时:{1}毫秒",

  1. 43: Thread.CurrentThread.ManagedThreadId,

  1. 44: sw1.ElapsedMilliseconds));

  1. 45: }

  1. 46: }

  1. 47: }

执行结果:

我们先不讨论结果,接着往下看看双检锁方式的性能。

实现3:双检锁实现的线程安全

Singleton双检锁实现:

  1. 1: using System;

  1. 2: using System.Collections.Generic;

  1. 3: using System.Linq;

  1. 4: using System.Text;

  1. 5: using System.Threading;

  1. 6: using System.Threading.Tasks;

  1. 7: 

  1. 8: namespace SingletonDoubleCheckThreadSafe

  1. 9: {

  1. 10: public sealed class Singleton2

  1. 11: {

  1. 12: private static Singleton2 instance = null;

  1. 13: private static readonly object _lock = new object();

  1. 14: 

  1. 15: private Singleton2()

  1. 16: {

  1. 17: }

  1. 18: 

  1. 19: public static Singleton2 Instance

  1. 20: {

  1. 21: get

  1. 22: {

  1. 23: if (instance == null)

  1. 24: {

  1. 25: lock (_lock)

  1. 26: {

  1. 27: if (instance == null)

  1. 28: {

  1. 29: instance = new Singleton2();

  1. 30: }

  1. 31: }

  1. 32: }

  1. 33: 

  1. 34: return instance;

  1. 35: }

  1. 36: }

  1. 37: }

  1. 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. 1: using System;

  1. 2: using System.Collections.Generic;

  1. 3: using System.Linq;

  1. 4: using System.Text;

  1. 5: using System.Threading.Tasks;

  1. 6: 

  1. 7: namespace SingletonNotUsingLock

  1. 8: {

  1. 9: public class Singleton

  1. 10: {

  1. 11: private volatile static Singleton instance = new Singleton();

  1. 12: 

  1. 13: // Explicit static constructor to tell C# compiler

  1. 14: // not to mark type as beforefieldinit

  1. 15: static Singleton()

  1. 16: {

  1. 17: Console.WriteLine("execute static constructor");

  1. 18: }

  1. 19: 

  1. 20: private Singleton()

  1. 21: {

  1. 22: Console.WriteLine("execute private constructor");

  1. 23: }

  1. 24: 

  1. 25: public static Singleton Instance

  1. 26: {

  1. 27: get

  1. 28: {

  1. 29: Console.WriteLine("instance get");

  1. 30: return instance;

  1. 31: }

  1. 32: }

  1. 33: }

  1. 34: }

上面的代码可以更简化一些,去掉Instance属性,将私有的instance变量改成public的:

  1. 1: using System;

  1. 2: using System.Collections.Generic;

  1. 3: using System.Linq;

  1. 4: using System.Text;

  1. 5: using System.Threading.Tasks;

  1. 6: 

  1. 7: namespace SingletonNotUsingLock

  1. 8: {

  1. 9: public class Singleton2

  1. 10: {

  1. 11: public volatile static Singleton2 instance = new Singleton2();

  1. 12: 

  1. 13: // Explicit static constructor to tell C# compiler

  1. 14: // not to mark type as beforefieldinit

  1. 15: static Singleton2()

  1. 16: {

  1. 17: Console.WriteLine("execute static constructor");

  1. 18: }

  1. 19: 

  1. 20: private Singleton2()

  1. 21: {

  1. 22: Console.WriteLine("execute private constructor");

  1. 23: }

  1. 24: }

  1. 25: }

代码非常简洁。但是为什么有个静态构造函数呢,我们看看下面的测试代码:

  1. 1: using System;

  1. 2: using System.Collections.Generic;

  1. 3: using System.Linq;

  1. 4: using System.Text;

  1. 5: using System.Threading.Tasks;

  1. 6: 

  1. 7: namespace SingletonNotUsingLock

  1. 8: {

  1. 9: class Program

  1. 10: {

  1. 11: static void Main(string[] args)

  1. 12: {

  1. 13: Console.WriteLine("begin create singleton");

  1. 14: 

  1. 15: Singleton s1 = Singleton.Instance;

  1. 16: 

  1. 17: Console.WriteLine("after create singleton");

  1. 18: 

  1. 19: Singleton2 s2 = Singleton2.instance;

  1. 20: 

  1. 21: Console.WriteLine("after create singleton2");

  1. 22: }

  1. 23: }

  1. 24: }

执行结果如下:

把静态构造函数去掉后执行结果如下:

这是因为没有静态构造函数的类,编译时会被标记称beforefieldinit,那么,beforefieldinit究竟表示什么样的语义呢?Scott Allen对此进行了详细的解释:beforefieldinit为CLR提供了在任何时候执行.cctor的授权,只要该方法在第一次访问类型的静态字段之前执行即可。

实现5:无锁懒加载

  1. 1: using System;

  1. 2: using System.Collections.Generic;

  1. 3: using System.Linq;

  1. 4: using System.Text;

  1. 5: using System.Threading.Tasks;

  1. 6: 

  1. 7: namespace SingletonNotUsingLockAndLazyLoad

  1. 8: {

  1. 9: public class Singleton

  1. 10: {

  1. 11: private Singleton()

  1. 12: {

  1. 13: Console.WriteLine("execute Singleton private constructor");

  1. 14: }

  1. 15: 

  1. 16: public static Singleton Instance

  1. 17: {

  1. 18:

  1. 19: get

  1. 20: {

  1. 21: Console.WriteLine("execute Singleton.Instance get");

  1. 22: return Nested.instance;

  1. 23: }

  1. 24: }

  1. 25: 

  1. 26: private class Nested

  1. 27: {

  1. 28: // Explicit static constructor to tell C# compiler

  1. 29: // not to mark type as beforefieldinit

  1. 30: static Nested()

  1. 31: {

  1. 32: Console.WriteLine("execute Nested static constructor");

  1. 33: }

  1. 34: 

  1. 35: internal static readonly Singleton instance = new Singleton();

  1. 36: }

  1. 37: }

  1. 38: }

实现6:使用.NET 4.0中的Lazy<T>

  1. 1: using System;

  1. 2: using System.Collections.Generic;

  1. 3: using System.Linq;

  1. 4: using System.Text;

  1. 5: using System.Threading.Tasks;

  1. 6: 

  1. 7: namespace SingletonUsingLazyType

  1. 8: {

  1. 9: public sealed class Singleton

  1. 10: {

  1. 11: private static readonly Lazy<Singleton> lazy =

  1. 12: new Lazy<Singleton>(() => new Singleton());

  1. 13: 

  1. 14: public static Singleton Instance { get { return lazy.Value; } }

  1. 15: 

  1. 16: private Singleton()

  1. 17: {

  1. 18: }

  1. 19: }

  1. 20: }

参考:

  1. Exploring the Singleton Design Pattern
  2. C#设计模式(7)-Singleton Pattern
  3. Implementing the Singleton Pattern in C#
  4. c#静态构造函数
  5. C# and beforefieldinit
  6. 《研磨设计模式》
  7. 关于Type Initializer和 BeforeFieldInit的问题,看看大家能否给出正确的解释
  8. [你必须知道的.NET]第二十三回:品味细节,深入.NET的类型构造器

设计模式之一:单例模式(Singleton Pattern)的更多相关文章

  1. 乐在其中设计模式(C#) - 单例模式(Singleton Pattern)

    原文:乐在其中设计模式(C#) - 单例模式(Singleton Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 单例模式(Singleton Pattern) 作者:weba ...

  2. 设计模式之单例模式(Singleton Pattern)

    单例模式 单例模式(Singleton Pattern)在java中算是最常用的设计模式之一,主要用于控制控制类实例的数量,防止外部实例化或者修改.单例模式在某些场景下可以提高系统运行效率.实现中的主 ...

  3. 【设计模式】单例模式 Singleton Pattern

    通常我们在写程序的时候会碰到一个类只允许在整个系统中只存在一个实例(Instance)  的情况, 比如说我们想做一计数器,统计某些接口调用的次数,通常我们的数据库连接也是只期望有一个实例.Windo ...

  4. 二十四种设计模式:单例模式(Singleton Pattern)

    单例模式(Singleton Pattern) 介绍保证一个类仅有一个实例,并提供一个访问它的全局访问点. 示例保证一个类仅有一个实例. Singleton using System; using S ...

  5. 乐在其中设计模式(C#) - 单例模式(Singleton Pattern)【转】

    介绍 保证一个类仅有一个实例,并提供一个访问它的全局访问点. 示例 保证一个类仅有一个实例. Singleton using System; using System.Collections.Gene ...

  6. Java 设计模式(三)-单例模式(Singleton Pattern)

    1     概念定义 1.1   定义 确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例. 1.2   类型 创建类模式 1.3   难点 1)多个虚拟机 当系统中的单例类被拷贝运行在多 ...

  7. python 设计模式之单例模式 Singleton Pattern

    #引入 一个类被设计出来,就意味着它具有某种行为(方法),属性(成员变量).一般情况下,当我们想使用这个类时,会使用new 关键字,这时候jvm会帮我们构造一个该类的实例.这么做会比较耗费资源. 如果 ...

  8. 【UE4 设计模式】单例模式 Singleton Pattern

    概述 描述 保证一个类只有一个实例 提供一个访问该实例的全局节点,可以视为一个全局变量 仅在首次请求单例对象时对其进行初始化. 套路 将默认构造函数设为私有, 防止其他对象使用单例类的 new运算符. ...

  9. 浅谈设计模式--单例模式(Singleton Pattern)

    题外话:好久没写blog,做知识归纳整理了.本来设计模式就是个坑,各种文章也写烂了.不过,不是自己写的东西,缺少点知识的存在感.目前还没做到光看即能记住,得写.所以准备跳入设计模式这个大坑. 开篇先贡 ...

  10. 设计模式系列之单例模式(Singleton Pattern)——确保对象的唯一性

    模式概述 模式定义 模式结构图 饿汉式单例与懒汉式单例 饿汉式单例 懒汉式单例 模式应用 模式在JDK中的应用 模式在开源项目中的应用 模式总结 主要优点 适用场景 说明:设计模式系列文章是读刘伟所著 ...

随机推荐

  1. js学习笔记32----new

    new:用于创建一个对象. 有 new 与 无 new 时的区别,查看下面的示例代码应该会增加感觉: <!DOCTYPE html> <html lang="en" ...

  2. js学习笔记18----元素创建操作

    1.父级.appendChild(新的元素) 从后面开始追加子元素. 2.父级.insertBefore(新的元素,被插入的元素) 在指定元素前面开始插入一个新元素. 兼容性:在ie下,如果第二个参数 ...

  3. laravel 控制器

    1:如何快速的创建一个控制器 用cmd进入当前的项目名文件夹里面执行如下语句: php artisan  make:controller  HgjController 2:编辑Hgj中的index方法 ...

  4. easyui Datagrid+searchbox 实现搜索功能

    1.前台页面 <%@ page language="java" pageEncoding="utf-8" isELIgnored="false& ...

  5. CSS边框-属性详解

    图解CSS padding.margin.border属性 W3C组织建议把所有网页上的对像都放在一个盒(box)中,设计师可以通过创建定义来控制这个盒的属性,这些对像包括段落.列表.标题.图片以及层 ...

  6. Linux中显示一个文件最后几行的命令

    tail -n 20 filename说明:显示filename最后20行. Linux下tail命令的使用方法.linux tail命令用途是依照要求将指定的文件的最后部分输出到标准设备,通常是终端 ...

  7. Omnigraffle快捷键

    cmd+shift+. 和 cmd+shift+,  放大缩小 按住z点击鼠标是放大, z+Option是缩小 按住command双指推移 缩放 按住command,旋转物体 按住 option缩放 ...

  8. Can't connect to MySQL server on '192.168.7.175' (10060)

    原因: 1.你的ip没有被授权,无法访问. 2.端口没有打开(如:3306端口没有打开). 解决方法: 授权(http://www.cnblogs.com/SZxiaochun/p/6401424.h ...

  9. e640. 使一个组件可拖动

    This example demonstrates the code needed to make a component draggable. The object being transferre ...

  10. ubuntu 访问 共享 windows文件夹

    sudo mount -o username=*******,password=******** //192.168.1.105/迅雷下载 /mnt/