單例模式介紹
單例模式主要解決的是,一個全局使用的類頻繁的創建和消費,從而提升整體代碼的性能。
在我們平時使用中,要確保一個類只能有一個實例對象,即使多線程同時訪問,也只能創建一個實例對象,並需要提供一個全局訪問此實例的點。
用來創建獨一無二的,只能有一個實例對象的入場卷。
單例模式允許在程序的任何地方訪問特定對象,但是它可以保護該實例不被其他代碼覆蓋。
使用場景:
- 控制某些共享資源的訪問權限(連接數據庫、訪問特殊文件)
- 某些全局的屬性或變量想保持其唯一性,可使用
- 程序中的某個類對於所有客戶端只有一個可用的實例,可以使用單例模式
代碼結構:
- 將默認的構造函數設為私有,防止其他對象使用單例類的new運算符。
- 會有一個靜態構造方法作為構造函數。該函數會偷偷調用私有構造函數來創建對象,並將其保存在一個靜態成員變量中。后面所有對該函數的調用都將返回這一緩存對象。
實現方式:
- 在類中聲明一個私有靜態成員變量用於保存單例模式
- 聲明一個公有靜態構建方法用於獲取單例
- 在靜態方法中實現“延遲初始化”,該方法會在首次被調用時創建一個新對象,並將其存儲在靜態成員變量中,此后該方法每次被調用時都返回該實例
- 將類的構造函數私有私有,防止其外部對象聲明調用
單例模式優缺點
優點:
- 可以保證一個類只有一個實例
- 獲得了一個指向該實例的全局訪問節點
- 僅在首次請求單例對象時對其進行初始化
缺點:
- 違反了“單一職責原則”(該模式同時解決了兩個問題)
- 該模式在多線程中需特殊處理,避免多個線程多次創建單例對象
- 單元測試比較困難,無法新建聲明新的測試對象。
Demo
單例可以分很多實現方式,但是從大類上來划分,主要為懶漢模式和餓漢模式
懶漢模式
- 懶漢模式(線程不安全)
/// <summary>
/// 單例模式 (常規用法,線程不安全。)
/// </summary>
public class Singleton
{
/// <summary>
/// 私有構造函數
/// </summary>
private Singleton()
{
}
/// <summary>
/// 靜態局部變量
/// </summary>
private static Singleton _instance=null;
/// <summary>
/// 靜態的全局唯一訪問口
/// 只能得到緩存的靜態局部變量實例,無法重新新建。
/// </summary>
/// <returns></returns>
public static Singleton GetInstance()
{
if (_instance==null)
{
_instance = new Singleton();
}
return _instance;
}
}
此方法滿足了懶加載,但是如果多個訪問者同時進行訪問獲取對象,就會出現多個實例對象,就不是單例模式了,所以此方法在多線程場景是不實用的。
- 懶漢模式(線程安全)
/// <summary>
/// 單例模式 (常規用法,線程安全。)
/// </summary>
public class Singleton
{
/// <summary>
/// 私有構造函數
/// </summary>
private Singleton()
{
}
/// <summary>
/// 靜態局部變量
/// </summary>
private static Singleton _instance=null;
/// <summary>
/// 聲明鎖 鎖同步
/// </summary>
private static readonly object _lock = new object();
/// <summary>
/// 靜態的全局唯一訪問口
/// 只能得到緩存的靜態局部變量實例,無法重新新建。
/// </summary>
/// <returns></returns>
public static Singleton GetInstance(string value)
{
if (_instance == null) //雙重判空,確保在多線程狀態下只創建一個實例對象
{
lock (_lock) //加鎖,確保其每次只能由一個對象進行訪問
{
if (_instance==null)
{
_instance = new Singleton();
_instance.Value = value;
}
}
}
return _instance;
}
public string Value { get; set; }
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("多線程進行訪問");
Thread processOne = new Thread(() => {
TestSingleton("阿輝");
});
Thread processTwo = new Thread(() => {
TestSingleton("阿七");
});
processOne.Start();
processTwo.Start();
processOne.Join();
processTwo.Join();
Console.ReadKey();
}
static void TestSingleton(string value)
{
Singleton s = Singleton.GetInstance(value);
Console.WriteLine(s.Value);
}
}
可以看到在我們模擬的多線程訪問過程中,即使設置的阿輝和阿七,最后輸出的也只有阿輝,也就是說只能創建一個實例對象。
可以看到懶加載就是程序剛開始不實例化,只有在被調用或者需要使用它的時候才進行實例化操作,這就是懶加載。
餓漢模式
- 使用靜態變量實現單例 ------餓漢模式(線程安全)
public class Singleton1
{
/// <summary>
/// 餓漢式,也就是在程序運行時都已經進行了初始化操作,后續只是調用而已。
/// </summary>
private static Singleton1 instance = new Singleton1();
private Singleton1()
{
}
public static Singleton1 GetInstance()
{
return instance;
}
}
餓漢式顧名思義就是提前都惡的不行了,在程序剛開始啟動的時候就已經將其進行了實例化,后續只管調用。
這種不是懶加載,無論程序是否會用到這個類,它都會提早的進行實例化。
- 利用靜態構造函數實現單例模式(線程安全)
public class Singleton2
{
private static Singleton2 _Singleton2 = null;
static Singleton2() {
_Singleton2 = new Singleton2();
}
public static Singleton2 CreateInstance()
{
return _Singleton2;
}
}
單例模式常用的就大體介紹完了,我們在平時的使用過程中需要根據不同的業務邏輯來選擇不同的實現方式,不能說哪一種最好,也不能說哪一種不好,只是它們使用的場景不同而已。
小寄語
人生短暫,我不想去追求自己看不見的,我只想抓住我能看的見的。
我是阿輝,感謝您的閱讀,如果對你有幫助,麻煩點贊、轉發 謝謝。