1、前言 單例模式屬於創建型模式,保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。 單例模式確保某一個類只有一個實例,而且自行實例化並向整個系統提供這個實例,這個類稱為單例類,它提供全局訪問的方法。 2、介紹 2.1、主要解決 防止一個系統全局使用的類頻繁地創建與銷毀、解決多線程併發訪問的問題 ...
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