什么是單例模式?
在一個項目中的全局范圍內(nèi), 一個類有且僅有一個實例對象。這個唯一的實例對象給其他模塊提供數(shù)據(jù)的全局訪問。這樣的模式就叫單例模式。
單例模式的典型例子就是任務(wù)隊列。
那么如何去實現(xiàn)這樣的一個單例模式的類?
首先, 考慮單例模式的要求為有且僅有一個實例對象。那么就先從構(gòu)造函數(shù)入手。類的構(gòu)造函數(shù)主要有:構(gòu)造函數(shù)、拷貝構(gòu)造函數(shù)、賦值運算符重載構(gòu)造函數(shù)。
- 對于構(gòu)造函數(shù),將構(gòu)造函數(shù)的訪問權(quán)限設(shè)為
private
, 這樣就禁止了構(gòu)造函數(shù)在類的外部被調(diào)用。而在類的內(nèi)部保證只調(diào)用構(gòu)造函數(shù)一次,這樣就創(chuàng)建了類的唯一對象。又因為單例對象需要提供數(shù)據(jù)的全局訪問,所以將這個唯一對象聲明為 靜態(tài)變量,靜態(tài)變量的生命周期為從創(chuàng)建開始直到程序結(jié)束。一般將靜態(tài)類的唯一對象設(shè)為private
(數(shù)據(jù)隱藏,封裝特性),而設(shè)計一個public
靜態(tài)函數(shù)提供訪問對象的唯一接口。關(guān)于類中的靜態(tài)變量和靜態(tài)函數(shù)的一些細節(jié):
- 類中靜態(tài)變量和靜態(tài)函數(shù)都屬于類,而不屬于任何類的對象。換句話說,一個類只有一個靜態(tài)變量和靜態(tài)函數(shù),多個對象共用這一個。
- 類的靜態(tài)變量和靜態(tài)函數(shù)在類中都可以直接訪問,但是在類的外部必須在前面加上類名和作用域運算符
::
;- 類的靜態(tài)變量在類的內(nèi)部創(chuàng)建但是在類的外部初始化,一般在類的定義中初始化;而類的靜態(tài)函數(shù)可以在類內(nèi)也可以在類外初始化。 類外初始化別忘記上一點說的加上類名和
::
- 類的靜態(tài)函數(shù)只能訪問靜態(tài)變量和靜態(tài)函數(shù),不能訪問非靜態(tài)變量和非靜態(tài)函數(shù)。所以單例模型類中訪問唯一對象的函數(shù)接口設(shè)為靜態(tài)的。
- 拷貝構(gòu)造函數(shù)、賦值運算符重載構(gòu)造函數(shù)給禁止掉或設(shè)為私有。這通過
=delete
實現(xiàn)。
所以,單例模式的類示例代碼如下:
// 定義一個單例模式的類
class Singleton
{
public:
// = delete 代表函數(shù)禁用,
Singleton(const Singleton& obj) = delete;//拷貝構(gòu)造函禁止
Singleton& operator=(const Singleton& obj) = delete;//賦值構(gòu)造函數(shù)禁止
static Singleton* getInstance();//靜態(tài)函數(shù)提供訪問唯一實例對象的唯一接口
private:
Singleton() = default;//構(gòu)造函數(shù)設(shè)為私有,并用default強調(diào)為系統(tǒng)默認的構(gòu)造函數(shù)
static Singleton* m_obj;//靜態(tài)對象
};
餓漢模式和懶漢模式
根據(jù)實例對象被創(chuàng)建的時機分為餓漢模型和懶漢模式。
- 餓漢模式
// 餓漢模式
class Singleton
{
public:
// = delete 代表函數(shù)禁用,
Singleton(const Singleton& obj) = delete;//拷貝構(gòu)造函禁止
Singleton& operator=(const Singleton& obj) = delete;//賦值構(gòu)造函數(shù)禁止
static Singleton* getInstance()//靜態(tài)函數(shù)提供訪問唯一實例對象的唯一接口
{
return m_obj;
}
private:
Singleton() = default;//構(gòu)造函數(shù)設(shè)為私有
static Singleton* m_obj;//靜態(tài)對象
};
// 靜態(tài)成員初始化放到類外部處理
Singleton* Singleton::m_obj = new Singleton;
int main()
{
Singleton* obj = Singleton::getInstance();
}
在餓漢模式下, 在類被加載時對象就被初始化。
- 懶漢模式
class Singleton
{
public:
// = delete 代表函數(shù)禁用,
Singleton(const Singleton& obj) = delete;//拷貝構(gòu)造函禁止
Singleton& operator=(const Singleton& obj) = delete;//賦值構(gòu)造函數(shù)禁止
static Singleton* getInstance();//靜態(tài)函數(shù)提供訪問唯一實例對象的唯一接口
{
if(m_obj == NULL)
{
m_obj = new Singleton();
return m_obj;
}
else return m_obj;
}
private:
Singleton() = default;//構(gòu)造函數(shù)設(shè)為私有
static Singleton* m_obj;//靜態(tài)對象
};
// 靜態(tài)成員初始化放到類外部處理
Singleton* Singleton::m_obj=NULL;
int main()
{
Singleton* obj = Singleton::getInstance();
}
懶漢模式下, 單例模式的對象在類加載不去創(chuàng)建,在被使用時才被創(chuàng)建。 所以懶漢模式是更省內(nèi)存的。
線程安全問題
餓漢模式是沒有線程安全問題的, 因為對象在類加載時就被創(chuàng)建出來。多個線程不能再創(chuàng)建新的對象,只能通過類提供的唯一接口訪問對象。
而懶漢模式會存在安全問題, 因為對象在使用時被創(chuàng)建。若多個線程同時使用的話可能就會創(chuàng)建多個對象。比如A線程第一次使用對象,使用if(m_obj == NULL)通過,下一步就是創(chuàng)建對象。而恰好此時時間片被線程B給占去,因為對象還為被創(chuàng)建,所以線程B也將開始創(chuàng)建對象,以此類推,所以這就可能創(chuàng)建多個對象,這就違背的單例模型的原則。
解決線程間數(shù)據(jù)同步的問題,最常用的辦法是互斥鎖。當(dāng)一個線程將互斥鎖鎖上的時候,其他線程不能再次上鎖,只能等該線程解鎖后才能進行上鎖和解鎖的操作。換句話說, 同時只能有一個線程持有互斥鎖。(一個坑位只能同時蹲一個人的意思 : )
- 互斥鎖
class Singleton//互斥鎖的解決方法
{
public:
// = delete 代表函數(shù)禁用,
Singleton(const Singleton& obj) = delete;//拷貝構(gòu)造函禁止
Singleton& operator=(const Singleton& obj) = delete;//賦值構(gòu)造函數(shù)禁止
static Singleton* getInstance();//靜態(tài)函數(shù)提供訪問唯一實例對象的唯一接口
{
m_mutex.lock();//鎖上,其他線程給我等著
if(m_obj == NULL)//互斥鎖內(nèi)的區(qū)域叫做臨界區(qū),該區(qū)域為原子操作,不能操作系統(tǒng)分段執(zhí)行。
{
m_obj = new Singleton();
return m_obj;
}
else return m_obj;
m_mutex.unlock();//解鎖,其他線程開始搶鎖
}
private:
Singleton() = default;//構(gòu)造函數(shù)設(shè)為私有
static Singleton* m_obj;//靜態(tài)對象
};
// 靜態(tài)成員初始化放到類外部處理
Singleton* Singleton::m_obj=NULL;
int main()
{
Singleton* obj = Singleton::getInstance();
}
由于互斥鎖的存在,所以多線程的并發(fā)性會在臨界區(qū)被狠狠的限制?。ㄒ淮沃荒苡幸粋€線程操作,其他線程被阻塞),這大大降低了代碼執(zhí)行的效率。
- 靜態(tài)局部對象
互斥鎖解決了線程安全的問題,但減低了代碼執(zhí)行的效率。但其實還有更好的方法可以解決線程安全的問題,那就是靜態(tài)局部變量。
class Singleton
{
public:
// = delete 代表函數(shù)禁用,
Singleton(const Singleton& obj) = delete;//拷貝構(gòu)造函禁止
Singleton& operator=(const Singleton& obj) = delete;//賦值構(gòu)造函數(shù)禁止
static Singleton* getInstance();//靜態(tài)函數(shù)提供訪問唯一實例對象的唯一接口
{
static Singleton m_obj;
return &m_obj;
}
private:
Singleton() = default;//構(gòu)造函數(shù)設(shè)為私有
static Singleton* m_obj;//靜態(tài)對象
};
// 靜態(tài)成員初始化放到類外部處理
Singleton* Singleton::m_obj=NULL;
int main()
{
Singleton* obj = Singleton::getInstance();
}
這定義了一個靜態(tài)的局部單例對象,并且將這個對象作為了唯一的單例實例。使用這種方式之所以是線程安全的,是因為在 C++11 標準中有如下規(guī)定,并且這個操作是在編譯時由編譯器保證的:(站在巨人的肩膀上就是舒服)
如果指令邏輯進入一個未被初始化的聲明變量,所有并發(fā)執(zhí)行應(yīng)當(dāng)?shù)却撟兞客瓿沙跏蓟?/code>
總結(jié):
懶漢模式的缺點是在創(chuàng)建實例對象的時候有安全問題(可以用互斥鎖或靜態(tài)局部變量解決),但這樣可以減少內(nèi)存的浪費(如果用不到就不去申請內(nèi)存了)。
餓漢模式則相反,在我們不需要這個實例對象的時候,它已經(jīng)被創(chuàng)建出來,占用了一塊內(nèi)存,但它不會存在線程安全問題。文章來源:http://www.zghlxwxcb.cn/news/detail-549293.html
最后用單例模式實現(xiàn)一個任務(wù)隊列
#include <iostream>
#include <queue>
#include <mutex>
#include <thread>
using namespace std;
class TaskQueue
{
public:
// = delete 代表函數(shù)禁用, 也可以將其訪問權(quán)限設(shè)置為私有
TaskQueue(const TaskQueue& obj) = delete;
TaskQueue& operator=(const TaskQueue& obj) = delete;
static TaskQueue* getInstance()//獲取任務(wù)隊列單例的唯一接口
{
return &m_obj;
}
// 任務(wù)隊列是否為空
bool isEmpty()
{
lock_guard<mutex> locker(m_mutex);//c++11特性,相當(dāng)于互斥鎖的上鎖和解鎖
bool flag = m_taskQ.empty();
return flag;
}
// 添加任務(wù)
void addTask(int data)
{
lock_guard<mutex> locker(m_mutex);
m_taskQ.push(data);
}
// 取出一個任務(wù)
int takeTask()
{
lock_guard<mutex> locker(m_mutex);
if (!m_taskQ.empty())
{
int res = m_taskQ.front();
m_taskQ.pop();
return res;
}
return -1;
}
// 刪除一個任務(wù)
bool popTask()
{
lock_guard<mutex> locker(m_mutex);
if (!m_taskQ.empty())
{
m_taskQ.pop();
return true;
}
return false;
}
private:
TaskQueue() = default;
static TaskQueue m_obj;//餓漢模式下的單例模式,類加載時就被創(chuàng)建,直到程序退出才清理
queue<int> m_taskQ;//維護的任務(wù)隊列
mutex m_mutex;
};
TaskQueue TaskQueue::m_obj;
int main()
{
thread t1([]() {//線程1不斷地往任務(wù)隊列尾部添加任務(wù)
TaskQueue* taskQ = TaskQueue::getInstance();//獲取類的唯一對象,該對象調(diào)用類通過的接口操作任務(wù)隊列。
for (int i = 0; i < 100; ++i)
{
taskQ->addTask(i + 100);
cout << "+++push task: " << i+100 << ", threadID: " << this_thread::get_id() << endl;
this_thread::sleep_for(chrono::milliseconds(500));
}
});
thread t2([]() {//線程2不斷從任務(wù)隊列頭部取出任務(wù)
TaskQueue* taskQ = TaskQueue::getInstance();
this_thread::sleep_for(chrono::milliseconds(100));//睡眠100ms,原子操作,不會被打斷
while (!taskQ->isEmpty())
{
int data = taskQ->takeTask();
cout << "---take task: " << data << ", threadID: "
<< this_thread::get_id() << endl;
this_thread::sleep_for(chrono::seconds(1));
}
});
t1.join();//線程1結(jié)束前阻塞在這,等線程結(jié)束后釋放線程1的資源
t2.join();
}
文章來源地址http://www.zghlxwxcb.cn/news/detail-549293.html
//懶漢模式
#include <iostream>
#include <queue>
#include <mutex>
#include <thread>
using namespace std;
class TaskQueue
{
public:
// = delete 代表函數(shù)禁用, 也可以將其訪問權(quán)限設(shè)置為私有
TaskQueue(const TaskQueue& obj) = delete;
TaskQueue& operator=(const TaskQueue& obj) = delete;
static TaskQueue& getInstance()
{
static TaskQueue m_obj;
return m_obj;
}
// 任務(wù)隊列是否為空
bool isEmpty()
{
lock_guard<mutex> locker(m_mutex);//c++11特性,相當(dāng)于互斥鎖的上鎖和解鎖
bool flag = m_taskQ.empty();
return flag;
}
// 添加任務(wù)
void addTask(int data)
{
lock_guard<mutex> locker(m_mutex);
m_taskQ.push(data);
}
// 取出一個任務(wù)
int takeTask()
{
lock_guard<mutex> locker(m_mutex);
if (!m_taskQ.empty())
{
int res = m_taskQ.front();
m_taskQ.pop();
return res;
}
return -1;
}
// 刪除一個任務(wù)
bool popTask()
{
lock_guard<mutex> locker(m_mutex);
if (!m_taskQ.empty())
{
m_taskQ.pop();
return true;
}
return false;
}
private:
TaskQueue() = default;
//static TaskQueue m_obj;//餓漢模式
queue<int> m_taskQ;
mutex m_mutex;
};
//TaskQueue TaskQueue::m_obj;
int main()
{
thread t1([]() {//線程1
TaskQueue& taskQ = TaskQueue::getInstance();//獲取單例任務(wù)隊列
for (int i = 0; i < 100; ++i)
{
taskQ.addTask(i + 100);
cout << "+++push task: " << i+100 << ", threadID: " << this_thread::get_id() << endl;
this_thread::sleep_for(chrono::milliseconds(500));
}
});
thread t2([]() {//線程2
TaskQueue& taskQ = TaskQueue::getInstance();
this_thread::sleep_for(chrono::milliseconds(100));//睡眠100ms,原子操作,不會被打斷
while (!taskQ.isEmpty())
{
int data = taskQ.takeTask();
cout << "---take task: " << data << ", threadID: "
<< this_thread::get_id() << endl;
this_thread::sleep_for(chrono::seconds(1));
}
});
t1.join();//線程1結(jié)束前阻塞在這,等線程結(jié)束后釋放線程1的資源
t2.join();
}
到了這里,關(guān)于【設(shè)計模式-單例模式】的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!