1、前言
單例模式屬于創建型模式,保證一個類僅有一個實體,并提供一個訪問它的全域訪問點,
單例模式確保某一個類只有一個實體,而且自行實體化并向整個系統提供這個實體,這個類稱為單例類,它提供全域訪問的方法,
2、介紹
2.1、主要解決
防止一個系統全域使用的類頻繁地創建與銷毀、解決多執行緒并發訪問的問題和節約系統記憶體等,提高系統運行的效率,提高系統性能,
什么情況需要使用全域的類?通常是對共享資源的使用,
比如需要實作系統控制列印機作業,一般都會定義一個“列印機管理類”用來管理列印機的各個功能,有多個模塊都需要控制列印機作業,在沒有使用單例模式的情況下,會遇到一下問題:
- 模塊會在需要列印時創建“列印機管理類”,列印完成即銷毀,如果需要頻繁列印,那么就會頻繁地創建和銷毀該類;
- 如果列印完成不銷毀呢?那么多個模塊都會創建“列印機管理類”,浪費系統資源;
- 一旦出現多個模塊同時需要列印,就要解決各模塊之間的列印同步問題,畢竟列印機只有一個,
使用單例模式后:
- 單例模式會自行創建,同時只提供一個全域訪問的類,因此不會造成系統資源的浪費,因此也沒有了頻繁地創建和銷毀的需求,
- 多個模塊同時需要列印時,只需要由全域的“列印機管理類”實體對列印的任務進行同步管理,無需多個模塊之間解決同步的問題,提高系統運行的效率,
2.2、優缺點
單例模式的使用需要根據實際情況使用,以下是該設計模式的優缺點,
優點:
- 在記憶體里只有一個實體,減少了記憶體的開銷,尤其是頻繁的創建和銷毀實體;
- 避免了多執行緒并發訪問時只需要該類管理同步即可,提高系統運行的效率,提高系統性能,
缺點:
- 沒有介面,不能繼承,與單一職責原則沖突;
- 單例模式可能掩蓋不良設計, 比如程式各組件之間相互了解過多等,
3、實作
在了解單例模式可以解決什么問題的情況下,那么如何實作一個單例模式呢?
- 在類中添加一個該類的私有靜態成員變數用于保存單例實體,
- 宣告一個公有靜態構建方法用于獲取單例實體,
- 將類的建構式設為私有,
3.1、懶漢設計
顧名思義,不到萬不得已就不會去實體化類,物件只有被呼叫時才去創建,這種方式是最基本的實作方式,但這種實作最大的問題就是不支持多執行緒,屬于執行緒不安全的,在多執行緒使用中,容易多次創建實體,因此嚴格的來說,并不算單例模式,
class CPrinter
{
public:
static CPrinter *GetInstance() // 提供全域訪問實體節點
{
if (ms_pinstance == nullptr)
{
ms_pinstance = new CPrinter();
}
return ms_pinstance;
}
private:
CPrinter(){}
~CPrinter(){}
private:
static CPrinter *ms_pinstance;
public:
void Work(void)
{
printf("[%p]Printer working\n", this);
}
};
CPrinter *CPrinter::ms_pinstance = nullptr;
int main()
{
CPrinter::GetInstance()->Work();
return 0;
}
為了解決在多執行緒使用中,容易多次創建實體的問題,可以加上互斥鎖,這樣就保證了執行緒安全,
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
class CPrinter
{
public:
static CPrinter *GetInstance() // 提供全域訪問實體節點
{
ms_initMutex.lock();
if (ms_pinstance == nullptr)
{
ms_pinstance = new CPrinter();
}
ms_initMutex.unlock();
return ms_pinstance;
}
private:
CPrinter(){}
~CPrinter(){}
private:
static CPrinter *ms_pinstance;
static std::mutex ms_initMutex;
public:
void Work(void)
{
printf("[%p]Printer working\n", this);
}
};
// 類外初始化
CPrinter *CPrinter::ms_pinstance = nullptr;
std::mutex CPrinter::ms_initMutex;
int main()
{
/* 創建執行緒1 */
std::thread([=]{
CPrinter::GetInstance()->Work();
}).detach();
/* 創建執行緒2 */
std::thread([=]{
CPrinter::GetInstance()->Work();
}).detach();
return 0;
}
3.2、懶漢設計+雙重校驗鎖
雖然上述的優化后的懶漢設計實作解決了執行緒安全的問題,但加鎖會影響效率,為了解決執行緒安全的問題,同時提高效率,采用雙重檢查加鎖機制,先判斷是否為 nullptr,如果不為空,則直接回傳實體,避免加鎖影響效率,而加鎖之后再判斷,是為了防止在加鎖之前多個執行緒都進入且競爭互斥鎖,避免下一個執行緒獲取鎖后再次創建實體,
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
class CPrinter
{
public:
static CPrinter *GetInstance() // 提供全域訪問實體節點
{
if (ms_pinstance == nullptr)
{
ms_initMutex.lock(); // 如果為空則加鎖
if (ms_pinstance == nullptr)
{
ms_pinstance = new CPrinter();
}
ms_initMutex.unlock();
}
return ms_pinstance;
}
private:
CPrinter(){}
~CPrinter(){}
private:
static CPrinter *ms_pinstance;
static std::mutex ms_initMutex;
public:
void Work(void)
{
printf("[%p]Printer working\n", this);
}
};
// 類外初始化
CPrinter *CPrinter::ms_pinstance = nullptr;
std::mutex CPrinter::ms_initMutex;
int main()
{
/* 創建執行緒1 */
std::thread([=]{
CPrinter::GetInstance()->Work();
}).detach();
/* 創建執行緒2 */
std::thread([=]{
CPrinter::GetInstance()->Work();
}).detach();
return 0;
}
3.3、餓漢設計
餓漢模式就是在單例類定義的時候(即在main函式之前)就進行實體化,因為main函式執行之前,全域作用域的類成員靜態變數ms_instance 已經初始化,因此就不存在多執行緒的問題,
#include <iostream>
#include <thread>
using namespace std;
class CPrinter
{
public:
static CPrinter *GetInstance() // 提供全域訪問實體節點
{
return &ms_instance;
}
private:
CPrinter(){}
~CPrinter(){}
private:
static CPrinter ms_instance;
public:
void Work(void)
{
printf("[%p]Printer working\n", this);
}
};
CPrinter CPrinter::ms_instance;
int main()
{
std::thread([=]{
CPrinter::GetInstance()->Work();
}).detach();
std::thread([=]{
CPrinter::GetInstance()->Work();
}).detach();
return 0;
}
本文來自博客園,作者:大橙子瘋,轉載請注明原文鏈接:https://www.cnblogs.com/const-zpc/p/16380282.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/498982.html
標籤:設計模式
上一篇:多系統對接的適配與包裝模式應用
下一篇:java觀察者模式的簡介說明
