單例模式
什么是單例模式?
單例模式屬于簡單設計模式的一種。在整個系統(tǒng)的生命周期內(nèi),單例類有且只有唯一一個對象,典型的應用比如日志的句柄。使用單例模式時需要考慮線程安全的問題,具體看后文具體的代碼解析。
單例模式的特點
- 單例類只能有一個實例。
- 成員是私有的、靜態(tài)的。
- 禁止拷貝、賦值,構造函數(shù)、私有函數(shù)是私有的。
單例模式的實現(xiàn)方式
- 懶漢模式:在需要使用的時候才實例化對象。
- 餓漢模式:在系統(tǒng)剛啟動時就實例化對象。
懶漢模式
實現(xiàn)一(非線程安全)
#include <iostream>
#include <array>
#include <thread>
#include <mutex>
#define MAX_THREAD_SIZE 10
class Singleton
{
public:
static Singleton* GetInstance();
static void DeleteInstance();
void Print(int index);
Singleton(const Singleton& kSingleton) = delete;
Singleton* operator=(const Singleton& kSingleton) = delete;
private:
Singleton();
~Singleton();
static Singleton* singleton_;
};
Singleton::Singleton()
{
std::cout << "構造函數(shù)" << std::endl;
}
Singleton::~Singleton()
{
std::cout << "析構函數(shù)" << std::endl;
}
Singleton* Singleton::GetInstance()
{
if (!singleton_)
{
singleton_ = new(std::nothrow) Singleton;
}
return singleton_;
}
void Singleton::DeleteInstance()
{
if (singleton_)
{
delete singleton_;
singleton_ = nullptr;
}
}
void Singleton::Print(int index)
{
std::cout << "線程" << index << ":" << this << std::endl;
}
Singleton* Singleton::singleton_ = nullptr;
int main()
{
std::array<std::thread, MAX_THREAD_SIZE> threads;
for (int i = 0; i < MAX_THREAD_SIZE; i++)
{
threads[i] = std::thread(&Singleton::Print, Singleton::GetInstance(), i);
threads[i].join();
}
Singleton::DeleteInstance();
return 0;
}
此種實現(xiàn)方式,可能會有兩個線程同時進入GetInstance()函數(shù),恰好同時判斷出singleton_指針為空,各自new了一個Singleton對象,所以是非線程安全的,如果想要此種實現(xiàn)是線程安全的,那么對GetInstance()實現(xiàn)加上鎖保護即可,詳見實現(xiàn)二。
實現(xiàn)二(線程安全)
#include <iostream>
#include <array>
#include <thread>
#include <mutex>
#define MAX_THREAD_SIZE 10
class Singleton
{
public:
static Singleton* GetInstance();
static void DeleteInstance();
void Print(int index);
Singleton(const Singleton& kSingleton) = delete;
Singleton* operator=(const Singleton& kSingleton) = delete;
private:
Singleton();
~Singleton();
static Singleton* singleton_;
static std::mutex mutex_;
};
Singleton::Singleton()
{
std::cout << "構造函數(shù)" << std::endl;
}
Singleton::~Singleton()
{
std::cout << "析構函數(shù)" << std::endl;
}
Singleton* Singleton::GetInstance()
{
if (!singleton_)
{
std::unique_lock<std::mutex> lock(mutex_);
if (!singleton_)
{
singleton_ = new(std::nothrow) Singleton;
}
}
return singleton_;
}
void Singleton::DeleteInstance()
{
std::unique_lock<std::mutex> lock(mutex_);
if (singleton_)
{
delete singleton_;
singleton_ = nullptr;
}
}
void Singleton::Print(int index)
{
std::cout << "線程" << index << ":" << this << std::endl;
}
Singleton* Singleton::singleton_ = nullptr;
std::mutex Singleton::mutex_;
int main()
{
std::array<std::thread, MAX_THREAD_SIZE> threads;
for (int i = 0; i < MAX_THREAD_SIZE; i++)
{
threads[i] = std::thread(&Singleton::Print, Singleton::GetInstance(), i);
threads[i].join();
}
Singleton::DeleteInstance();
return 0;
}
通過在GetInstance()函數(shù)中添加鎖的保護,可以保證有且只有一個線程進入并創(chuàng)建了Singleton類對象,從而保證了線程安全,但是多了鎖的開銷,那么有沒有更好的方法呢?下面介紹C++11后最推薦的方式。
實現(xiàn)三(線程安全、推薦)
#include <iostream>
#include <array>
#include <thread>
#include <mutex>
#define MAX_THREAD_SIZE 10
class Singleton
{
public:
static Singleton& GetInstance();
void Print(int index);
Singleton(const Singleton& kSingleton) = delete;
Singleton& operator=(const Singleton& kSingleton)= delete;
private:
Singleton();
~Singleton();
};
Singleton::Singleton()
{
std::cout << "構造函數(shù)" << std::endl;
}
Singleton::~Singleton()
{
std::cout << "析構函數(shù)" << std::endl;
}
Singleton& Singleton::GetInstance()
{
static Singleton singleton_;
return singleton_;
}
void Singleton::Print(int index)
{
std::cout << "線程" << index << ":" << this << std::endl;
}
int main()
{
std::array<std::thread, MAX_THREAD_SIZE> threads;
for (int i = 0; i < MAX_THREAD_SIZE; i++)
{
threads[i] = std::thread(&Singleton::Print, &Singleton::GetInstance(), i);
threads[i].join();
}
return 0;
}
此種實現(xiàn)適用于C++11之后的程序,因為C++11規(guī)定:如果當變量在初始化的時候,并發(fā)同時進入聲明語句,并發(fā)線程將會阻塞等待初始化結束。 這種返回局部靜態(tài)變量的方式,更加的簡潔高效,所以比較推薦。
餓漢模式
#include <iostream>
#include <array>
#include <thread>
#include <mutex>
#define MAX_THREAD_SIZE 10
class Singleton
{
public:
static Singleton* GetInstance();
static void DeleteInstance();
void Print(int index);
Singleton(const Singleton& kSingleton) = delete;
Singleton& operator=(const Singleton& kSingleton) = delete;
private:
Singleton();
~Singleton();
static Singleton* singleton_;
};
Singleton::Singleton()
{
std::cout << "構造函數(shù)" << std::endl;
}
Singleton::~Singleton()
{
std::cout << "析構函數(shù)" << std::endl;
}
Singleton* Singleton::GetInstance()
{
return singleton_;
}
void Singleton::DeleteInstance()
{
if (singleton_)
{
delete singleton_;
singleton_ = nullptr;
}
}
void Singleton::Print(int index)
{
std::cout << "線程" << index << ":" << this << std::endl;
}
Singleton* Singleton::singleton_ = new(std::nothrow) Singleton;
int main()
{
std::array<std::thread, MAX_THREAD_SIZE> threads;
for (int i = 0; i < MAX_THREAD_SIZE; i++)
{
threads[i] = std::thread(&Singleton::Print, Singleton::GetInstance(), i);
threads[i].join();
}
Singleton::DeleteInstance();
return 0;
}
餓漢模式的實現(xiàn),在程序啟動時,就已經(jīng)實例化了Singleton對象,因此,后續(xù)訪問的都是同一個對象,是天然的線程安全的。
總結
單例模式是一種比較經(jīng)典、常用的設計模式,面試也經(jīng)常會問到,是一定要掌握的。如果在程序中需要創(chuàng)建一個唯一存在的實例對象,那么一定要考慮使用單例模式,優(yōu)先使用懶漢模式中的返回局部靜態(tài)變量的方法,切記保證線程安全。
浙公網(wǎng)安備 33010602011771號