設計模式筆記:單件模式(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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM