1、单例模式顾名思义,单个实例,在整个程序或者系统的生命周期当中,有且只有一个该类的实例(对象)。
1、整个系统或者程序只需要一个对象、控件时,如:window系统任务管理器,配置文件类等;
2、需要频繁创建和销毁对象时,如:工具类,日志类等;
3、频繁访问IO资源的,如:数据库连接池,线程连接池等。
懒汉式:当使用时才创建实列;(需要考虑线程安全)
饿汉式:系统开始运行时创建实列,等待调用。(本身线程安全)
1、构造函数和析构函数设置为private,禁止外部构造和析构。
2、拷贝构造和赋值构造函数设置为private,禁止外部使用拷贝和赋值。
3、获取实列函数设置为静态(static),可以全局访问。
1、饿汉式
#ifndef SINGLETON_H
#define SINGLETON_H
class Singleton
{
public:
static Singleton* getInstance();//获取实例
void func(); //成员函数
private:
Singleton(); //禁止外部调用构造函数
~Singleton(); //禁止外部调用析构函数
Singleton(const Singleton &singl); //禁止外部调用拷贝构造函数
const Singleton &operator=(const Singleton &singl); //禁止外部调用赋值函数
private:
static Singleton* m_Singleton; //实例
};
#endif // SINGLETON_H
#include "Singleton.h"
#include
//静态变量初始化
Singleton* Singleton::m_Singleton = new Singleton();
Singleton::Singleton()
{
std::cout<<"Singleton 构造函数被调用。"<
#include
#include "Singleton.h"
using namespace std;
//main文件
int main()
{
std::cout<<"main 函数开始执行"<func();
return 0;
}
程序运行结果:
结果分析:
main文件不做任何操作,Singleton类在一开始就初始化创建实例了,不给线程不安全机会呀。
2、懒汉式(单线程)
//H文件
#ifndef SINGLETON_H
#define SINGLETON_H
class Singleton
{
public:
static Singleton* getInstance();//获取实例
void printInstanceAddr(); //成员函数
private:
Singleton(); //禁止外部调用构造函数
~Singleton(); //禁止外部调用析构函数
Singleton(const Singleton &singl); //禁止外部调用拷贝构造函数
const Singleton &operator=(const Singleton &singl); //禁止外部调用赋值函数
private:
static Singleton* m_Singleton; //实例
};
#endif // SINGLETON_H
//CPP文件
#include "Singleton.h"
#include
Singleton* Singleton::m_Singleton = NULL;
Singleton::Singleton()
{
std::cout<<"Singleton 构造函数被调用。"<
//main函数
#include
#include "Singleton.h"
int main()
{
Singleton::getInstance()->printInstanceAddr();
return 0;
}
结果:
结果分析:
在单线程的环境下,普通懒汉式还是安全的,在需要的时候进行实例化,确实相比来说懒点。
3、懒汉式(多线程)
首先先看多线程下普通懒汉模式是如何不安全的
#ifndef SINGLETON_H
#define SINGLETON_H
class Singleton
{
public:
static Singleton* getInstance();//获取实例
void printInstanceAddr(); //成员函数
private:
Singleton(); //禁止外部调用构造函数
~Singleton(); //禁止外部调用析构函数
Singleton(const Singleton &singl); //禁止外部调用拷贝构造函数
const Singleton &operator=(const Singleton &singl); //禁止外部调用赋值函数
private:
static Singleton* m_Singleton; //实例
};
#endif // SINGLETON_H
#include "Singleton.h"
#include
Singleton* Singleton::m_Singleton = NULL;
Singleton::Singleton()
{
std::cout<<"Singleton 构造函数被调用。"<
#include
#include "Singleton.h"
#include
#include
// 线程函数
void* threadOperateFunc(void* arg)
{
// 主线程与子线程分离
pthread_detach(pthread_self());
// 打印实例地址
Singleton::getInstance()->printInstanceAddr();
//线程终止函数
pthread_exit(NULL);
}
int main(void)
{
std::cout << "main 函数被调用" << std::endl;
pthread_t threads[5] = {0};
for (int i = 0; i < 5; i++)
{
std::cout << "创建第"<
结果:
结果分析 :
结果中会看到创建了两个实例,为什么会出现这个情况呢?因为如果在一个线程刚刚m_Singleton为判断NULL准备创建实例变量时,又有一个线程对m_Singleton进行判断,这时因为还未创建好实例所以判断也为NULL,所以两个线程都会创建实例,所以就会出现两个实例。
怎么解决这个办法呢?
造成这个问题的原因是变量m_Singleton未具有互斥属性,临界资源未得到保护,怎么办呢,加锁。
#ifndef SINGLETON_H
#define SINGLETON_H
#include
class Singleton
{
public:
static Singleton* getInstance();//获取实例
void printInstanceAddr(); //成员函数
private:
Singleton(); //禁止外部调用构造函数
~Singleton(); //禁止外部调用析构函数
Singleton(const Singleton &singl); //禁止外部调用拷贝构造函数
const Singleton &operator=(const Singleton &singl); //禁止外部调用赋值函数
private:
static Singleton* m_Singleton; //实例
static std::mutex tex;
};
#endif // SINGLETON_H
#include "Singleton.h"
#include
#include
Singleton* Singleton::m_Singleton = NULL;
std::mutex Singleton::tex;
Singleton::Singleton()
{
std::cout<<"Singleton 构造函数被调用。"< lck(tex);
if(!m_Singleton)
{
m_Singleton = new Singleton();
}
}
return m_Singleton;
}
void Singleton::printInstanceAddr()
{
std::cout<<"实例内存地址是:" << this << std::endl;
}
#include
#include "Singleton.h"
#include
#include
// 线程函数
void* threadOperateFunc(void* arg)
{
// 主线程与子线程分离
pthread_detach(pthread_self());
// 打印实例地址
Singleton::getInstance()->printInstanceAddr();
//线程终止函数
pthread_exit(NULL);
}
int main(void)
{
std::cout << "main 函数被调用" << std::endl;
pthread_t threads[5] = {0};
for (int i = 0; i < 5; i++)
{
std::cout << "创建第"<
结果:
结果分析:
从结果上来看只创建了一个实例,线程安全。虽热双检锁减少了单独加锁的开销,但是还是有书写麻烦等缺点。根据c++11特性有一种新的实现方式。
C++11标准中的Magic Static特性:
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
若变量在初始化时,并发同时进入声明语句,并发线程将会阻塞等待初始化结束。
这样可以保证并发线程在获取静态局部变量的时候一定是初始化过的,所以具有线程安全性。
利用局部静态变量,实现线程安全的懒汉单例模式。
#ifndef SINGLETON_H
#define SINGLETON_H
#include
class Singleton
{
public:
static Singleton* getInstance();//获取实例
void printInstanceAddr(); //成员函数
private:
Singleton(); //禁止外部调用构造函数
~Singleton(); //禁止外部调用析构函数
Singleton(const Singleton &singl); //禁止外部调用拷贝构造函数
const Singleton &operator=(const Singleton &singl); //禁止外部调用赋值函数
private:
};
#endif // SINGLETON_H
#include "Singleton.h"
#include
#include
Singleton::Singleton()
{
std::cout<<"Singleton 构造函数被调用。"<
#include
#include "Singleton.h"
#include
#include
// 线程函数
void* threadOperateFunc(void* arg)
{
// 主线程与子线程分离,两者相互不干涉,子线程结束同时子线程的资源自动回收
pthread_detach(pthread_self());
// 打印实例地址
Singleton::getInstance()->printInstanceAddr();
pthread_exit(NULL);
}
int main(void)
{
std::cout << "main 函数被调用" << std::endl;
pthread_t threads[5] = {0};
for (int i = 0; i < 5; i++)
{
std::cout << "创建第"<
结果:
结果分析
只有一个实例线程安全。代码简洁,方便编写。
如何禁止类的拷贝构造函数和赋值构造函数?
1、将拷贝构造函数和赋值构造函数声明为私有,如本文中的实现。
2、将拷贝构造函数和赋值构造函数后面添加 = delete,
例如:
Singleton(const Singleton &singl) = delete;
const Singleton &operator=(const Singleton &singl) = delete;
1、单例模式的内存回收问题。
2、加锁的懒汉单例模式就真的多线程安全吗。
3、智能指针。
本文知识浅浅的发表了对单例模式的一些想法,后面如果有更深的理解或者有更好的想法会继续完善的。