c 线程安全的单例模式-设计模式之单例模式(C++版)

2022-12-29 13:38:41 浏览数 (1)

  什么是单例模式?

  单例模式是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种模式方法。

  单例特点:

  1 在任何情况下,单例类永远只有一个实例存在。

  2 单例需要有能力为整个系统提供这一唯一实例。

  示例:打印机,任务管理器等。

  实现一(单线程使用,多线程不安全)

   #include

代码语言:javascript复制
    using namespace std;
    class Singleton
    {
    private:
        Singleton(){}
    public:
        static Singleton* instance()
        {
            if(_instance == 0)
                _instance = new Singleton();
            return _instance;
        }
    private:
        static Singleton* _instance;
    };
    Singleton* Singleton::_instance = 0;

  上面这种实现在单线程环境下是没有问题的,可是多线程下就有问题了。

  分析:

  1 线程A进入函数执行判断语句,这句执行后就挂起了,这时线程A已经认为为NULL,但是线程A还没有创建对象。

  2 又有一个线程B进入函数执行判断语句,此时同样认为变量为null,因为A没有创建对象。线程B继续执行,创建了一个对象。

  3 稍后,线程A接着执行,也创建了一个新的对象。

  4 创建了两个对象!

  从上面分析可以看出,需要对变量加上互斥锁:

  实现二(多线程安全,加锁代价高)

   #include

代码语言:javascript复制
    #include 
    using namespace std;
    std::mutex mt;
    class Singleton
    {
    private:
        Singleton(){}
    public:
        static Singleton* instance()
        {
            mt.lock();  // 加锁
            if(_instance == 0)
                _instance = new Singleton();
            mt.unlock();  // 解锁
            return _instance;
        }
    private:
        static Singleton* _instance;
    };
    Singleton* Singleton::_instance = 0;

  上锁后是解决了线程安全问题,但是有些资源浪费。稍微分析一下:每次函数调用时候都需要请求加锁,其实并不需要,函数只需第一次调用的时候上锁就行了。这时可以用DCLP解决。

  实现三(双检查锁,由于内存读写导致不安全)

  Double-

   #include

代码语言:javascript复制
    #include 
    using namespace std;
    std::mutex mt;
    class Singleton
    {
    
    private:
        Singleton(){}
    public:
        static Singleton* instance()
        {
            if(_instance == 0)
            {
                mt.lock();
                if(_instance == 0)
                    _instance = new Singleton();
                mt.unlock();
            }
            return _instance;
        }
    private:
        static Singleton* _instance;
    public:
        int atestvalue;
    };
    Singleton* Singleton::_instance = 0;

  这个版本很不错,又叫“双重检查”Double-Check。下面是说明:

  第一个条件是说,如果实例创建了,那就不需要同步了,直接返回就好了。不然,我们就开始同步线程。第二个条件是说,如果被同步的线程中,有一个线程创建了对象,那么别的线程就不用再创建了。

  分析

  _instance = new Singleton();

  为了执行这句代码,机器需要做三样事儿:

  1.对象分配空间。

  2.在分配的空间中构造对象

  3.使指向分配的空间

  遗憾的是编译器并不是严格按照上面的顺序来执行的。可以交换2和3.

  将上面三个步骤标记到代码中就是这样:

   Singleton* Singleton::instance() {

代码语言:javascript复制
        if (_instance == 0) {
            mt.lock();
            if (_instance == 0) {
                _instance = // Step 3
                operator new(sizeof(Singleton)); // Step 1
                new (_instance) Singleton; // Step 2
            }
            mt.unlock();
        }
        return _instance;
    }

  实现四(C 11版本最简洁的跨平台方案)(推荐版本)

  Meyers

  局部静态变量不仅只会初始化一次,而且还是线程安全的。

代码语言:javascript复制
#include 
using namespace std;
class Singleton
{
public:
    // 注意返回的是引用
    static Singleton& getInstance()
    {
        static Singleton value;  //静态局部变量
        return value;
    }
private:
    Singleton() = default;
    Singleton(const Singleton& other) = delete; //禁止使用拷贝构造函数
    Singleton& operator=(const Singleton&) = delete; //禁止使用拷贝赋值运算符

};
int main()
{
    Singleton& s1 = Singleton::getInstance();
    cout 
[1]: https://xuan.ddwoo.top/index.php/archives/556/
[2]: https://xuan.ddwoo.top/index.php/archives/558/
[3]: https://xuan.ddwoo.top/index.php/archives/549/
[4]: https://xuan.ddwoo.top/index.php/archives/555/                
        本文共 647 个字数,平均阅读时长 ≈ 2分钟

0 人点赞