熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> .NET編程 >> 正文

Singleton設計模式的C#實現

2022-06-13   來源: .NET編程 
Singleton模式

   Singleton(譯為單件或單態)模式是設計模式中比較簡單而常用的模式

   有些時候在整個應用程序中會要求某個類有且只有一個實例這個時候可以采用Singleton模式進行設計用Singleton模式設計的類不僅能保證在應用中只有一個實例而且提供了一種非全局變量的方法進行全局訪問稱為全局訪問點這樣對於沒有全局變量概念的純面向對象語言來說是非常方便的比如C#

   本文用一個計數器的例子來描述在C#中如何使用Singleton模式計數的值設計為計數器類的一個私有成員變量它被個不同的線程進行讀寫操作為保證計數的正確性在整個應用當中必然要求計數器類的實例是唯一的

Singleton的實現方式

   首先看看教科書方式的Singleton標准實現的兩種方法以下用的是類C#偽代碼

  方法一

using System;
namespace csPatternSingleton
{
public class Singleton
{
static Singleton uniSingleton = new Singleton();
private Singleton() {}
static public Singleton instance()
{
return uniSingleton;
}
}
}
方法二

using System;
namespace csPatternSingleton
{
public class Singleton
{
static Singleton uniSingleton;
private Singleton() {}
static public Singleton instance()
{
if (null == uniSingleton)
{
uniSingleton = new Singleton _lazy();
}
return uniSingleton;
}
}
}
  Singleton模式的實現有兩個技巧一是使用靜態成員變量保存全局的實例確保了唯一性使用靜態的成員方法instance() 代替 new關鍵字來獲取該類的實例達到全局可見的效果二是將構造方法設置成為private如果使用new關鍵字創建類的實例則編譯報錯以防編程時候筆誤

    上面方法二的初始化方式稱為lazy initialization是在第一次需要實例的時候才創建類的實例與方法一中類的實例不管用不用一直都有相比方法二更加節省系統資源但是方法二在多線程應用中有時會出現多個實例化的現象
  假設這裡有個線程主線程和線程在創建類的實例的時候可能會遇到一些原因阻塞一段時間(比如網絡速度或者需要等待某些正在使用的資源的釋放)此時的運行情況如下

  主線程首先去調用instance()試圖獲得類的實例instance()成員方法判斷該類沒有創建唯一實例於是開始創建實例由於一些因素主線程不能馬上創建成功而需要等待一些時間此時線程也去調用instance()試圖獲得該類的實例因為此時實例還未被主線程成功創建因此線程又開始創建新實例結果是兩個線程分別創建了兩次實例對於計數器類來說就會導致計數的值被重置與Singleton的初衷違背解決這個問題的辦法是同步

   下面看看本文的計數器的例子的實現

  使用方法一

using System;
using SystemThreading;
namespace csPatternSingleton
{
public class Counter
{
static Counter uniCounter = new Counter(); //存儲唯一的實例
private int totNum = ; //存儲計數值
private Counter()
{
ThreadSleep(); //這裡假設因為某種因素而耽擱了毫秒
//在非lazy initialization 的情況下 不會影響到計數
}
static public Counter instance()
{
return uniCounter;
}
public void Inc() { totNum ++;} //計數加
public int GetCounter() { return totNum;} //獲得當前計數值
}
}
  以下是調用Counter類的客戶程序在這裡我們定義了四個線程同時使用計數器每個線程使用最後得到的正確結果應該是

using System;
using SystemIO;
using SystemThreading;
namespace csPatternSingletonMutileThread
{
public class MutileClient
{
public MutileClient() {}
public void DoSomeWork()
{
Counter myCounter = Counterinstance(); //方法一
//Counter_lazy myCounter = Counter_lazyinstance(); //方法二
for (int i = ; i < ; i++)
{
myCounterInc();
ConsoleWriteLine(線程{}報告: 當前counter為: {} ThreadCurrentThreadNameToString() myCounterGetCounter()ToString());
}
}
public void ClientMain()
{
Thread thread = ThreadCurrentThread;
threadName = Thread ;
Thread thread =new Thread(new ThreadStart(thisDoSomeWork));
threadName = Thread ;
Thread thread =new Thread(new ThreadStart(thisDoSomeWork));
threadName = Thread ;
Thread thread =new Thread(new ThreadStart(thisDoSomeWork));
threadName = Thread ;
threadStart();
threadStart();
threadStart();
DoSomeWork(); //線程也只執行和其他線程相同的工作
}
}
}
  以下為Main函數本程序的測試入口

using System;
namespace csPatternSingleton
{
public class RunMain
{
public RunMain() {}
static public void Main(string[] args)
{
MutileThreadMutileClient myClient = new MutileThreadMutileClient();
myClientClientMain();
SystemConsoleReadLine();
}
}
}
  執行結果如下

   線程Thread 報告: 當前counter為:
   線程Thread 報告: 當前counter為:
   線程Thread 報告: 當前counter為:
   線程Thread 報告: 當前counter為:
   線程Thread 報告: 當前counter為:
   線程Thread 報告: 當前counter為:
   線程Thread 報告: 當前counter為:
   線程Thread 報告: 當前counter為:
   線程Thread 報告: 當前counter為:
   線程Thread 報告: 當前counter為:
   線程Thread 報告: 當前counter為:
   線程Thread 報告: 當前counter為:
   線程Thread 報告: 當前counter為:
   線程Thread 報告: 當前counter為:
   線程Thread 報告: 當前counter為:
   線程Thread 報告: 當前counter為:

   由於系統線程調度的不同每次的執行結果也不同但是最終結果一定是

   方法一中由於實例一開始就被創建所以instance()方法無需再去判斷是否已經存在唯一的實例而返回該實例所以不會出現計數器類多次實例化的問題

使用方法二

using System;
using SystemThreading;
using SystemRuntimeCompilerServices;
namespace csPatternSingleton
{
public class Counter_lazy
{
static Counter_lazy uniCounter;
private int totNum = ;
private Counter_lazy()
{
ThreadSleep(); //假設多線程的時候因某種原因阻塞毫秒
}
[MethodImpl(MethodImplOptionsSynchronized)] //方法的同步屬性
static public Counter_lazy instance()
{
if (null == uniCounter)
{
uniCounter = new Counter_lazy();
}
return uniCounter;
}
public void Inc() { totNum ++;}
public int GetCounter() { return totNum;}
}
}
  不知道大家有沒有注意到instance()方法上方的[MethodImpl(MethodImplOptionsSynchronized)] 語句他就是同步的要點他指定了instance()方法同時只能被一個線程使用這樣就避免了線程調用instance()創建完成實例前線程就來調用instance()試圖獲得該實例

   根據MSDN的提示也可以使用lock關鍵字進行線程的加鎖代碼如下

using System;
using SystemThreading;
namespace csPatternSingleton
{
public class Counter_lazy
{
static Counter_lazy uniCounter;
static object myObject = new object();
private int totNum = ;
private Counter_lazy()
{
ThreadSleep(); //假設多線程的時候因某種原因阻塞毫秒
}
static public Counter_lazy instance()
{
lock(myObject)
{
if (null == uniCounter)
{
uniCounter = new Counter_lazy();
}
return uniCounter;
}
}
public void Inc() { totNum ++;}
public int GetCounter() { return totNum;}
}
}

  lock()是對一個對象加互斥鎖只允許一個線程訪問其後大括號中語句塊直到該語句塊的代碼執行完才解鎖解鎖後才允許其他的線程執行其語句塊

  還可以使用Mutex類進行同步定義private static Mutex mut = new Mutex();後修改instance()如下同樣可以得到正確的結果

static public Counter_lazy instance()
{
mutWaitOne();
if (null == uniCounter)
{
uniCounter = new Counter_lazy();
}
mutReleaseMutex();
return uniCounter;
}

  注意的是本例中使用方法二要更改方法一的客戶程序去掉Counter_lazyintance()的注釋並將Counterintance()注釋

  singleton模式還可以拓展只要稍加修改就可以限制在某個應用中只能允許m個實例存在而且為m個實例提供全局透明的訪問方法
From:http://tw.wingwit.com/Article/program/net/201311/12545.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.