zoukankan      html  css  js  c++  java
  • 设计模式笔记:单件模式(Singleton)

    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
    {
        /// <summary>
        /// 单件模式实现方式:由于该实现方式非线程安全,在实际应用中不推荐使用。
        /// </summary>
        public sealed class Singleton
        {
            // 定义一个静态变量来保存类的实例
            private static Singleton _instance;
    
            // 私有构造函数,防止通过new实例化对象
            private Singleton()
            {
            }
    
            /// <summary>
            /// 定义公有静态方法,获取实例,并加入判断,保证实例只被创建一次
            /// </summary>
            /// <returns></returns>
            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
    {
        /// <summary>
        /// 单件模式实现方式:由于该实现方式非线程安全,在实际应用中不推荐使用。
        /// </summary>
        public sealed class Singleton
        {
            // 定义一个静态变量来保存类的实例
            private static Singleton _instance;
    
            // 私有构造函数,防止通过new实例化对象
            private Singleton()
            {
            }
    
            /// <summary>
            /// 定义公有静态方法,获取实例,并加入判断,保证实例只被创建一次
            /// </summary>
            /// <returns></returns>
            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:63835064
    Singleton:6044116

    (2)简单线程安全

    using System;
    using System.Collections.Generic;
    using System.Text;
    
    namespace Libing.DesignPatterns.SingletonPattern.Structural
    {
        /// <summary>
        /// 单件模式实现方式:简单线程安全。
        /// </summary>
        public sealed class Singleton
        {
            // 定义一个静态变量来保存类的实例
            private static Singleton _instance;
    
            // 定义一个标识确保线程同步
            private static readonly object _syncLock = new object();
    
            // 私有构造函数,防止通过new实例化对象
            private Singleton()
            {
            }
    
            /// <summary>
            /// 定义公有静态方法,获取实例,并加入判断,保证实例只被创建一次
            /// </summary>
            /// <returns></returns>
            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
    {
        /// <summary>
        /// 单件模式实现方式:双重锁定线程安全。
        /// </summary>
        public sealed class Singleton
        {
            // 定义一个静态变量来保存类的实例
            private static Singleton _instance;
    
            // 定义一个标识确保线程同步
            private static readonly object _syncLock = new object();
    
            // 私有构造函数,防止通过new实例化对象
            private Singleton()
            {
            }
    
            /// <summary>
            /// 定义公有静态方法,获取实例,并加入判断,保证实例只被创建一次
            /// </summary>
            /// <returns></returns>
            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. 参考资料

      http://www.dofactory.com/Patterns/Patterns.aspx

  • 相关阅读:
    【Codeforce 487E】【UOJ#30】—Tourists(圆方树+树链剖分)
    【省选模拟】—River(贪心)
    【BZOJ4012】【HNOI2015】—开店(动态点分治)
    【BZOJ4543】【POI2014】Hotel加强版(长链剖分)
    【BZOJ3809】—GTY的二逼妹子序列(莫队+权值分块)
    【BZOJ2878】【NOI2012】—迷失游乐园(基环树期望dp)
    【2019省选模拟】—树(并查集+容斥)
    【BZOJ2120】—数颜色(带修莫队)
    【BZOJ3597】【SCOI2014】—方伯伯运椰子(分数规划)
    【BZOJ3598】【SCOI2014】方伯伯的商场之旅(数位dp)
  • 原文地址:https://www.cnblogs.com/libingql/p/2797532.html
Copyright © 2011-2022 走看看