博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
设计模式笔记:单件模式(Singleton)
阅读量:7027 次
发布时间:2019-06-28

本文共 8385 字,大约阅读时间需要 27 分钟。

1. 单件模式简介

1.1 定义

  单件模式(Singleton)定义:要求一个类有且仅有一个实例,并且提供了一个全局的访问点,在同一时刻只能被一个线程所访问。

  单件模式的特点:

  (1)单件类只能有一个实例。
  (2)单件类必须自身创建唯一实例。
  (3)单件类必须给所有其它对象提供唯一实例。

1.2 使用频率

   中高

2、单件模式结构

2.1 结构图

2.2 参与者

  单件模式参与者:

  ◊ Singleton

    ° 被调用的单件对象;

    ° 在单件模式中,通常由Instance()或GetInstance()方法负责对象的创建,该方法应保证每个需要(单件)对象的客户端均能访问。

3. 单件模式结构实现

3.1 单件模式实现要点

  ◊ 单件类有一个私有的无参构造函数,防止被其他类实例化。

  ◊ 单件类不能被继承,使用sealed修饰。

  ◊ 单件类使用静态的变量保存单实例的引用。

  ◊ 单件类使用公有静态方法获取单一实例的引用,如果实例为null即创建一个。

3.2 C#代码

(1)非线程安全

  主要实现原理:在不考虑并发的情况下,通过以下2点实现单一实例。

  (a) 静态变量和静态方法在内存中唯一。

  (b) 私有构造函数确保不能通过调用构造函数来生成实例。

  Singleton.cs

using System;using System.Collections.Generic;using System.Text;namespace Libing.DesignPatterns.SingletonPattern.Structural{    ///     /// 单件模式实现方式:由于该实现方式非线程安全,在实际应用中不推荐使用。    ///     public sealed class Singleton    {        // 定义一个静态变量来保存类的实例        private static Singleton _instance;        // 私有构造函数,防止通过new实例化对象        private Singleton()        {        }        ///         /// 定义公有静态方法,获取实例,并加入判断,保证实例只被创建一次        ///         /// 
public static Singleton Instance() { // 使用延迟初始化 // 若类的实例不存在则创建实例,若存在则返回实例 // 注: 非线程安全 if (_instance == null) { _instance = new Singleton(); } return _instance; } }}

  Program.cs

using System;using System.Collections.Generic;using System.Linq;using System.Text;using Libing.DesignPatterns.SingletonPattern.Structural;namespace Libing.DesignPatterns.SingletonPattern{    class Program    {        static void Main(string[] args)        {            // 创建一个实例s1            Singleton s1 = Singleton.Instance();            // 创建一个实例s2            Singleton s2 = Singleton.Instance();            if (s1 == s2)            {                Console.WriteLine("对象为相同实例");            }        }    }}

  运行输出:

对象为相同实例请按任意键继续. . .

注:以上的实现方式适用于单线程环境,在多线程的环境下有可能得到Singleton类的多个实例。假如同时有两个线程去判断(null == _singleton),并且得到的结果为真,那么两个线程都会创建类Singleton的实例,这样就违背了Singleton模式“唯一实例”的原则。

   多线程测试:

using System;using System.Collections.Generic;using System.Text;using System.Threading;namespace Libing.DesignPatterns.SingletonPattern.Structural{    ///     /// 单件模式实现方式:由于该实现方式非线程安全,在实际应用中不推荐使用。    ///     public sealed class Singleton    {        // 定义一个静态变量来保存类的实例        private static Singleton _instance;        // 私有构造函数,防止通过new实例化对象        private Singleton()        {        }        ///         /// 定义公有静态方法,获取实例,并加入判断,保证实例只被创建一次        ///         /// 
public static Singleton Instance() { // 使用延迟初始化 // 若类的实例不存在则创建实例,若存在则返回实例 // 注: 非线程安全 if (_instance == null) { Thread.Sleep(1000); // 模拟线程阻塞 _instance = new Singleton(); } return _instance; } }}
using System;using System.Threading;using Libing.DesignPatterns.SingletonPattern.Structural;namespace Libing.DesignPatterns.SingletonPattern{    class Program    {        static void Main(string[] args)        {            Thread t1 = new Thread(new ThreadStart(Display));            t1.Start();            Thread t2 = new Thread(new ThreadStart(Display));            t2.Start();        }        public static void Display()        {            Singleton s = Singleton.Instance();            Console.WriteLine("Singleton:" + s.GetHashCode());        }    }}

  运行结果:

Singleton:63835064Singleton:6044116

(2)简单线程安全

using System;using System.Collections.Generic;using System.Text;namespace Libing.DesignPatterns.SingletonPattern.Structural{    ///     /// 单件模式实现方式:简单线程安全。    ///     public sealed class Singleton    {        // 定义一个静态变量来保存类的实例        private static Singleton _instance;        // 定义一个标识确保线程同步        private static readonly object _syncLock = new object();        // 私有构造函数,防止通过new实例化对象        private Singleton()        {        }        ///         /// 定义公有静态方法,获取实例,并加入判断,保证实例只被创建一次        ///         /// 
public static Singleton Instance() { // 当第一个线程运行到这里时,此时会对_syncLock对象 "加锁", // 当第二个线程运行该方法时,首先检测到_syncLock对象为"加锁"状态,该线程就会挂起等待第一个线程解锁 // lock语句运行完之后(即线程运行完之后)会对该对象"解锁" lock (_syncLock) { // 使用延迟初始化 // 若类的实例不存在则创建实例,若存在则返回实例 if (_instance == null) { _instance = new Singleton(); } } return _instance; } }}

  以上方式的实现方式是线程安全的,首先创建了一个静态只读的进程辅助对象,由于lock是确保当一个线程位于代码的临界区时,另一个线程不能进入临界区(同步操作)。如果其他线程试图进入锁定的代码,则它将一直等待,直到该对象被释放。从而确保在多线程下不会创建多个对象实例了。

  但这种实现方式要进行同步操作,将影响系统性能的瓶颈和增加了额外的开销。

(3)双重锁定线程安全

  在上面简单线程安全代码中,对于每个线程都会对线程辅助对象locker加锁之后再判断实例是否存在。

  对于这个操作完全没有必要的,因为当第一个线程创建了该类的实例之后,后面的线程此时只需要直接判断(uniqueInstance==null)为假,而不必要对线程辅助对象加锁之后再去判断,所以上面的实现方式增加了额外的开销,损失了性能。

  为了改进上面实现方式的缺陷,只需要在lock语句前面加一句(uniqueInstance==null)的判断即可避免锁所增加的额外开销,这种实现方式称为“双重锁定”。

using System;using System.Collections.Generic;using System.Text;namespace Libing.DesignPatterns.SingletonPattern.Structural{    ///     /// 单件模式实现方式:双重锁定线程安全。    ///     public sealed class Singleton    {        // 定义一个静态变量来保存类的实例        private static Singleton _instance;        // 定义一个标识确保线程同步        private static readonly object _syncLock = new object();        // 私有构造函数,防止通过new实例化对象        private Singleton()        {        }        ///         /// 定义公有静态方法,获取实例,并加入判断,保证实例只被创建一次        ///         /// 
public static Singleton Instance() { // 当第一个线程运行到这里时,此时会对_syncLock对象 "加锁", // 当第二个线程运行该方法时,首先检测到_syncLock对象为"加锁"状态,该线程就会挂起等待第一个线程解锁 // lock语句运行完之后(即线程运行完之后)会对该对象"解锁" // 双重锁定只需要一句判断即可 if (_instance == null) { lock (_syncLock) { // 使用延迟初始化 // 若类的实例不存在则创建实例,若存在则返回实例 if (_instance == null) { _instance = new Singleton(); } } } return _instance; } }}

  NUnit测试:

using System;using System.Collections.Generic;using System.Reflection;using System.Text;using Libing.DesignPatterns.SingletonPattern.Structural;using NUnit.Framework;namespace Libing.DesignPatterns.SingletonPattern.Tests{    [TestFixture]    public class SingletonTests    {        [Test]        public void TestCreateSingleton()        {            Singleton s1 = Singleton.Instance();            Singleton s2 = Singleton.Instance();            Assert.AreSame(s1, s2);        }        [Test]        public void TestNoPublicConstructors()        {            Type singleton = typeof(Singleton);            ConstructorInfo[] ctrs = singleton.GetConstructors();            bool hasPublicConstructor = false;            foreach (ConstructorInfo c in ctrs)            {                if (c.IsPublic)                {                    hasPublicConstructor = true;                    break;                }            }            Assert.IsFalse(hasPublicConstructor);        }    }}

  Microsoft.VisualStudio.TestTools.UnitTesting测试:

using System;using System.Reflection;using Microsoft.VisualStudio.TestTools.UnitTesting;using Libing.DesignPatterns.SingletonPattern.Structural;namespace Libing.DesignPatterns.SingletonPattern.Tests{    [TestClass]    public class SingletonTests    {        [TestMethod]        public void TestCreateSingleton()        {            Singleton s1 = Singleton.Instance();            Singleton s2 = Singleton.Instance();            Assert.AreSame(s1, s2);        }        [TestMethod]        public void TestNoPublicConstructors()        {            Type singleton = typeof(Singleton);            ConstructorInfo[] ctrs = singleton.GetConstructors();            bool hasPublicConstructor = false;            foreach (ConstructorInfo c in ctrs)            {                if (c.IsPublic)                {                    hasPublicConstructor = true;                    break;                }            }            Assert.IsFalse(hasPublicConstructor);        }    }}

4. 单件模式应用分析

4.1 单件模式使用注意点

  (1)不要使用单例模式存取全局变量。这违背了单例模式的用意,最好放到对应类的静态成员中。

  (2)不要将数据库连接做成单例,因为一个系统可能会与数据库有多个连接,并且在有连接池的情况下,应当尽可能及时释放连接。Singleton模式由于使用静态成员存储类实例,所以可能会造成资源无法及时释放。

4.2 单件模式适用情形

  (1)当类只能有一个实例而且客户可以从一个众所周知的访问点访问时;

  (2)当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能适用一个扩展的实例时。

5. 参考资料

  

转载于:https://www.cnblogs.com/libingql/archive/2012/12/01/2797532.html

你可能感兴趣的文章
NAT64与DNS64基本原理概述
查看>>
Java-Shiro(四):Shiro
查看>>
Oracle 备份、恢复单表或多表数据步骤
查看>>
Windows安装Linux子系统--安装GUI界面
查看>>
raise EnvironmentError("%s not found" % (mysql_config.path,)) EnvironmentError: mysql_config not...
查看>>
Easy Web Development Framework for Java
查看>>
python多线程学习 - Rollen Holt - 博客园
查看>>
Redis命令总结
查看>>
字符串分割(C++)
查看>>
window.parent与window.openner区别
查看>>
oracle - 函数
查看>>
面试体验:Microsoft 篇
查看>>
转载 - 超实用的8个Linux命令行性能监测工具
查看>>
【转载】8天学通MongoDB——第八天 驱动实践
查看>>
局网满猿关不住,一波码农出墙来。
查看>>
关于厦门宽带用户升级了FTTH光纤入户,从别的地方拿来的ITV机顶盒没有办法使用的解决办法...
查看>>
Head First 设计模式笔记:(一)策略模式
查看>>
学习活动图的制作
查看>>
HTTP 错误 404.2 - Not Found 由于 Web 服务器上的“ISAPI 和 CGI 限制”列表设置,无法提供您请求的页面...
查看>>
Oracle 跟踪SQL 查询 及清除 v$SqlArea
查看>>