单例模式及单例类的两种实现
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。单例模式同时也是所有设计模式中最简单的一种。
那么问题来了,单例类有什么作用呢?对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。
如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机。
简单来说。单例模式就是说系统中对于某类的只能有一个对象,不可能出来第二个。下面以模板的形式实现两种单例类,基于模板通用性会更好,否则对于每个类都要实现自己的单例模式:
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
//单例类模板
template <class T>
class Singleton
{
public:
Singleton()//可放在private
{
}
static T* instance()//返回某类型的单例对象
{
if(m_pInstance == NULL)//这里进行了两遍检查
{
pthread_mutex_lock(&mutex);//上锁
if(m_pInstance == NULL)//保证只能执行一次
{
T* temp = new T;//构造一个新的对象
m_pInstance = temp;
}
pthread_mutex_unlock(&mutex);//解锁
}
return m_pInstance;//返回该类型的单例对象
}
private:
static T* m_pInstance;
static pthread_mutex_t mutex;//需要互斥锁
};
template<class T>
T* Singleton<T>::m_pInstance = NULL;
template<class T>
pthread_mutex_t Singleton<T>::mutex;
class Test:public Singleton<Test>
{//使用继承的方式
public:
Test()
{
cout<<"我只会打印一次"<<endl;
}
};
void* thread1(void*)//第一组线程的线程函数
{
cout<<"thread1 正在运行"<<endl;
Test* test1 = Test::instance();//调用该函数,但是Test对象只会创建一次
}
void* thread2(void *)//第二组线程的线程函数
{
cout<<"thread2 正在运行"<<endl;
Test* test2 = Test::instance();//调用该函数,但是Test对象只会创建一次
}
int main()
{
const unsigned int thread_num = 5;
pthread_t thread_id[thread_num];
for(unsigned int i = 0; i<thread_num;i++)//创建1组5个线程
{
pthread_create(&thread_id[i], NULL, thread1, NULL);
}
for(unsigned int i = 0; i<thread_num;i++)//创建另一组的五个线程
{
pthread_create(&thread_id[i], NULL, thread2, NULL);
}
sleep(1);//等待线程结束
return 0;
}
该程序分两次分别创建了5个线程,在每个线程中分别调用Test::instance()获取单例类对象,两个线程线程函数会分别运行5次,但是对象只会构造一次,其余的并没有构造新的对象,只是返回该对象而已,运行结果如下:
在linux中其实还有一种更加方便的方式,这也是muduo库里面的实现方式(muduo库的实现大家可以直接取看源码,其实和下面的大同小异),因为linux里为我们提供了pthread_once函数,这种单例模式比较通用,而且在多线程环境下也很稳定,该函数原型如下:
#include <pthread.h>
int pthread_once(pthread_once_t *once_control,void (*init_routine)(void));
pthread_once_t once_control = PTHREAD_ONCE_INIT;
pthread_once函数第一个参数是一个pthread_once_t类型的变量,该变量可以保证init_routine函数只被执行一次,所以使用该函数会使单例类的实现变得简单许多,看下面的例子
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
template<class T>
class Singleton
{
public:
static T& instance()//返回单例类对象
{//原型是int pthread_once(pthread_once_t *once_control,void (*init_routine)(void));
//init_routine函数只可能被执行一次
pthread_once(&m_Once, &Singleton::init);
return *m_tValue;
}
private:
Singleton();//构造函数
~Singleton();//析构函数
static void init()//该函数只被执行一次
{
m_tValue = new T();//在内部创建的对象
}
private:
static pthread_once_t m_Once;//该对象保证某个函数只被执行一次
static T* m_tValue;
};
template<typename T>
pthread_once_t Singleton<T>::m_Once = PTHREAD_ONCE_INIT;
template<typename T>
T* Singleton<T>::m_tValue = NULL;
class Test
{
public:
Test()
{
cout<<"虽然创建了10个线程,但是我只会打印一次"<<endl;
}
};
void* thread_func(void*)
{
cout<<"thread_func正在执行"<<endl;
Test& t = Singleton<Test>::instance();//创建一个Test类型的单例对象
}
int main(int argc, char const *argv[])
{
pthread_t thread_id[10];
for(int i=0;i<10;i++)//创建10个线程
{
pthread_create(&thread_id[i], NULL, thread_func, NULL);//在每个线程的线程函数里创建Test单例对象
}
sleep(1);//稍作延时等待线程结束
return 0;
}
该程序和第一个例子实现的功能差不多,不过只是这次一次性创建10个线程,运行结果和之前的大同小异