C++实现线程安全单例类

来源:互联网 发布:彩票计划软件 编辑:程序博客网 时间:2024/06/05 23:55

1.什么是单例模式


单例模式是一种非常常用的设计模式,几乎在稍大的工程项目中都会用到。单例类保证在全局中只有唯一一个类的实例对象,并且在类的内部提供了获取这个唯一实例的接口。在类中,实例化出一个对象需要调用构造函数,为了防止在类的外部调用构造函数构造出实例,对类的构造函数就应有所限制,可以将构造函数的访问权限设置为private或protected。还要提供一个访问实例的接口,在类中定义一个static函数,返回类内部唯一的实例对象即可。

2.构造一个实例类


class Singleton{private:    Singleton()   //构造函数声明为私有的        :_a(0)    {}    //防拷贝    Singleton(const Singleton&);    Singleton& operator=(const Singleton&);public:    static Singleton* GetInstance()   //声明一个静态类型的接口来获取实例    {        if (_inst == NULL)        {            _inst =  new Singleton();        }        return _instance;    }private:    int _a;    //指向实例的指针定义为静态私有,这样定义静态成员函数获取对象实例    static Singleton* _instance;};Singleton* Singleton::_instance= NULL;   //初始化静态成员变量

构造函数能创建一个实例对象,但是拷贝构造函数和赋值拷贝构造函数同样可以,所以,可以用防拷贝的方法只声明不定义,避免了多次创建实例对象。
上面的代码,看起来貌似没有什么问题,但是若是在多线程下,有可能会创建多个Singleton实例,所以为了防止这种情况,我们必须保证创建对象的过程时原子操作,则可以考虑加锁。C++11的mutix头文件中已经提供了lock和unlock,我们可以直接使用。

#include <mutex>class Singleton{private:    Singleton()   //构造函数声明为私有的        :_a(0)    {}    //防拷贝    Singleton(const Singleton&);    Singleton& operator=(const Singleton&);public:    static Singleton* GetInstance()   //声明一个静态类型的接口来获取实例    {        if (_instance== NULL)        {            lock_guard<mutex> lock(_mtx);            if (_instance== NULL)            {                _inst = new Singleton();            }        }        return _instance;    }    void DelInstance()   //销毁实例    {        lock_guard<mutex> lock(_mtx);        if (_instance)        {            delete _instance;            _inst = NULL;        }    }private:    int _a;    static Singleton* _instance;//声明一个静态成员作为类的实例    static mutex _mtx;//保证线程安全的互斥锁};Singleton* Singleton::_instance = NULL;   //初始化静态成员变量mutex Singleton::_mtx;

以上代码中加了互斥锁,保证了线程安全。但也不是绝对安全的,因为加锁或解锁会引入新的问题,有可能抛异常或者造成死锁。解决了线程安全,那么就可以进一步提高效率。在上述代码中,可以看到用了两次判断,也叫双检查机制,这就保证了只在第一次获取对象时加锁,避免高并发场景下每次获取实例对象都进行加锁,提高了效率。

3.具体模式


懒汉模式:在第一次调用GetInstance的时候才实例化出对象,此后返回的都是该对象。相对饿汉模式而言,复杂,要确保线程安全问题,但在各种场景下都适用。
饿汉模式:无论是否需要该类的实例,在程序一开始的时候会产生该类的实例对象,此后返回的都是该对象。由于是在main函数之前创建线程,可能会出现不确定问题,适用性受到限制。
上面代码实现的是懒汉模式,那么下边就实现饿汉模式

namespace Hungry{    class Singleton    {    private:        Singleton()   //构造函数声明为私有的            :_a(0)        {}        //防拷贝        Singleton(const Singleton&);        Singleton& operator=(const Singleton&);    public:        //static Singleton& GetInstance()           //{        //  assert(_instance);        //  return *_instance;        //}        static Singleton& GetInstance()     //在类内部创建静态实例,全局只此一份        {            static Singleton _instance;              return _instance;        }        void DelInstance()   //销毁实例        {            if (_instance)            {                delete _instance;                _instance = NULL;            }        }    private:        int _a;        static Singleton* _instance;    };    Singleton* Singleton::_instance = NULL;       //Singleton* Singleton::_instance = new Singleton();//全局生成一个实例对象   }

定义为全局或者静态的变量和对象都是在调用main()函数之前就已经初始化好的,此时只有一个线程,所以不会存在线程安全的问题。

4.双检查隐患


指令流水作业时,系统可能会为了提高效率,将执行流的顺序打乱,这时候进行外部的检查时,可能内部的代码还没有完全执行完,但是已经满足了外部的判断条件,那么执行就会出错。
比如,创建一个实例对象,正确的执行流是:
1.operator new创建空间
2.调用构造函数初始化成员变量
3.赋值
打乱之后的:
1.operator new创建空间
2.赋值
3.调用构造函数初始化成员变量
这时候为了避免这种情况,可以使用内存屏障MemoryBarrier,保证其之前和之后的顺序流不被打乱。

static Singleton* GetInstance(){    if (_instance == NULL)    {        lock_guard<mutex> lock(_mtx);          if (_instance == NULL)        {            Singleton* tmp = new Singleton;            MemoryBarrier();   //使用内存栅栏            _instance = tmp;        }    }    return _instance;}

参考:http://www.jellythink.com/archives/82

原创粉丝点击