在擁有共享數據的多條線程並行執行的程序中,線程安全的代碼會經過同步機制保證各個線程均可以正常且正確的執行,不會出現數據污染等意外狀況。ios
單例模式指在整個系統生命週期裏,保證一個類只能產生一個實例,確保該類的惟一性。c++
單例模式能夠分爲懶漢式和餓漢式,二者之間的區別在於建立實例的時間不一樣:數組
/////////////////// 普通懶漢式實現 -- 線程不安全 //////////////////
#include <iostream> // std::cout
#include <mutex> // std::mutex
#include <pthread.h> // pthread_create
class SingleInstance {
public:
// 獲取單例對象
static SingleInstance *GetInstance();
// 釋放單例,進程退出時調用
static void deleteInstance();
// 打印單例地址
void Print();
private:
// 將其構造和析構成爲私有的, 禁止外部構造和析構
SingleInstance();
~SingleInstance();
// 將其拷貝構造和賦值構形成爲私有函數, 禁止外部拷貝和賦值
SingleInstance(const SingleInstance &signal);
const SingleInstance &operator=(const SingleInstance &signal);
private:
// 惟一單例對象指針
static SingleInstance *m_SingleInstance;
};
//初始化靜態成員變量
SingleInstance *SingleInstance::m_SingleInstance = NULL;
SingleInstance* SingleInstance::GetInstance()
{
if (m_SingleInstance == NULL)
{
m_SingleInstance = new (std::nothrow) SingleInstance; // 沒有加鎖是線程不安全的,當線程併發時會建立多個實例
}
return m_SingleInstance;
}
void SingleInstance::deleteInstance()
{
if (m_SingleInstance)
{
delete m_SingleInstance;
m_SingleInstance = NULL;
}
}
void SingleInstance::Print()
{
std::cout << "個人實例內存地址是:" << this << std::endl;
}
SingleInstance::SingleInstance()
{
std::cout << "構造函數" << std::endl;
}
SingleInstance::~SingleInstance()
{
std::cout << "析構函數" << std::endl;
}
/////////////////// 普通懶漢式實現 -- 線程不安全 //////////////////
// 線程函數
void *PrintHello(void *threadid) {
// 主線程與子線程分離,二者相互不干涉,子線程結束同時子線程的資源自動回收
pthread_detach(pthread_self());
// 對傳入的參數進行強制類型轉換,由無類型指針變爲整形數指針,而後再讀取
int tid = *((int *)threadid);
std::cout << "Hi, 我是線程 ID:[" << tid << "]" << std::endl;
// 打印實例地址
SingleInstance::GetInstance()->Print();
pthread_exit(NULL);
}
#define NUM_THREADS 5 // 線程個數
int main(void) {
pthread_t threads[NUM_THREADS] = {0};
int indexes[NUM_THREADS] = {0}; // 用數組來保存i的值
int ret = 0;
int i = 0;
std::cout << "main() : 開始 ... " << std::endl;
for (i = 0; i < NUM_THREADS; i++)
{
std::cout << "main() : 建立線程:[" << i << "]" << std::endl;
indexes[i] = i; //先保存i的值
// 傳入的時候必須強制轉換爲void* 類型,即無類型指針
ret = pthread_create(&threads[i], NULL, PrintHello, (void *)&(indexes[i]));
if (ret)
{
std::cout << "Error:沒法建立線程," << ret << std::endl;
exit(-1);
}
}
// 手動釋放單實例的資源
SingleInstance::deleteInstance();
std::cout << "main() : 結束! " << std::endl;
return 0;
}
複製代碼
從運行結果可知,單例構造函數建立了兩個個,內存地址分別爲0x7f3c980008c0
和0x7f3c900008c0
,因此普通懶漢式單例只適合單進程不適合多線程,由於是線程不安全的。安全
/////////////////// 加鎖的懶漢式實現 //////////////////
class SingleInstance {
public:
// 獲取單實例對象
static SingleInstance *&GetInstance();
//釋放單實例,進程退出時調用
static void deleteInstance();
// 打印實例地址
void Print();
private:
// 將其構造和析構成爲私有的, 禁止外部構造和析構
SingleInstance();
~SingleInstance();
// 將其拷貝構造和賦值構形成爲私有函數, 禁止外部拷貝和賦值
SingleInstance(const SingleInstance &signal);
const SingleInstance &operator=(const SingleInstance &signal);
private:
// 惟一單實例對象指針
static SingleInstance *m_SingleInstance;
static std::mutex m_Mutex;
};
//初始化靜態成員變量
SingleInstance *SingleInstance::m_SingleInstance = NULL;
std::mutex SingleInstance::m_Mutex;
SingleInstance *&SingleInstance::GetInstance()
{
// 這裏使用了兩個 if判斷語句的技術稱爲雙檢鎖;好處是,只有判斷指針爲空的時候才加鎖,
// 避免每次調用 GetInstance的方法都加鎖,鎖的開銷畢竟仍是有點大的。
if (m_SingleInstance == NULL)
{
std::unique_lock<std::mutex> lock(m_Mutex); // 加鎖
if (m_SingleInstance == NULL)
{
m_SingleInstance = new (std::nothrow) SingleInstance;
}
}
return m_SingleInstance;
}
void SingleInstance::deleteInstance()
{
std::unique_lock<std::mutex> lock(m_Mutex); // 加鎖
if (m_SingleInstance)
{
delete m_SingleInstance;
m_SingleInstance = NULL;
}
}
void SingleInstance::Print()
{
std::cout << "個人實例內存地址是:" << this << std::endl;
}
SingleInstance::SingleInstance()
{
std::cout << "構造函數" << std::endl;
}
SingleInstance::~SingleInstance()
{
std::cout << "析構函數" << std::endl;
}
/////////////////// 加鎖的懶漢式實現 //////////////////
複製代碼
從運行結果可知,只建立了一個實例,內存地址是0x7f28b00008c0
,因此加了互斥鎖的普通懶漢式是線程安全的bash
/////////////////// 內部靜態變量的懶漢實現 //////////////////
class Single {
public:
// 獲取單實例對象
static Single &GetInstance();
// 打印實例地址
void Print();
private:
// 禁止外部構造
Single();
// 禁止外部析構
~Single();
// 禁止外部複製構造
Single(const Single &signal);
// 禁止外部賦值操做
const Single &operator=(const Single &signal);
};
Single &Single::GetInstance()
{
// 局部靜態特性的方式實現單實例
static Single signal;
return signal;
}
void Single::Print()
{
std::cout << "個人實例內存地址是:" << this << std::endl;
}
Single::Single()
{
std::cout << "構造函數" << std::endl;
}
Single::~Single()
{
std::cout << "析構函數" << std::endl;
}
/////////////////// 內部靜態變量的懶漢實現 //////////////////
複製代碼
-std=c++0x
編譯是使用了C++11的特性,在C++11內部靜態變量的方式裏是線程安全的,只建立了一次實例,內存地址是0x6016e8
,這個方式很是推薦,實現的代碼最少!多線程
[root@lincoding singleInstall]#g++ SingleInstance.cpp -o SingleInstance -lpthread -std=c++0x
複製代碼
////////////////////////// 餓漢實現 /////////////////////
class Singleton {
public:
// 獲取單實例
static Singleton* GetInstance();
// 釋放單實例,進程退出時調用
static void deleteInstance();
// 打印實例地址
void Print();
private:
// 將其構造和析構成爲私有的, 禁止外部構造和析構
Singleton();
~Singleton();
// 將其拷貝構造和賦值構形成爲私有函數, 禁止外部拷貝和賦值
Singleton(const Singleton &signal);
const Singleton &operator=(const Singleton &signal);
private:
// 惟一單實例對象指針
static Singleton *g_pSingleton;
};
// 代碼一運行就初始化建立實例 ,自己就線程安全
Singleton* Singleton::g_pSingleton = new (std::nothrow) Singleton;
Singleton* Singleton::GetInstance()
{
return g_pSingleton;
}
void Singleton::deleteInstance()
{
if (g_pSingleton)
{
delete g_pSingleton;
g_pSingleton = NULL;
}
}
void Singleton::Print()
{
std::cout << "個人實例內存地址是:" << this << std::endl;
}
Singleton::Singleton()
{
std::cout << "構造函數" << std::endl;
}
Singleton::~Singleton()
{
std::cout << "析構函數" << std::endl;
}
////////////////////////// 餓漢實現 /////////////////////
複製代碼
從運行結果可知,餓漢式在程序一開始就構造函數初始化了,因此自己就線程安全的 併發