zoukankan      html  css  js  c++  java
  • C++模式设计-多线程下的单例模式

    1 教科书里的单例模式

    我们都很清楚一个简单的单例模式该怎样去实现:构造函数声明为private或protect防止被外部函数实例化,内部保存一个private static的类指针保存唯一的实例,实例的动作由一个public的类方法代劳,该方法也返回单例类唯一的实例。

    上代码: 

    class singleton
    {
    protected:
      singleton(){}
    private:
      static singleton* p;
    public:
      static singleton* instance();
    };
    singleton* singleton::p = NULL;
    singleton* singleton::instance()
    {
      if (p == NULL)
        p = new singleton();
      return p;
    }

    这是一个很棒的实现,简单易懂。但这是一个完美的实现吗?不!该方法是线程不安全的,考虑两个线程同时首次调用instance方法且同时检测到p是NULL值,则两个线程会同时构造一个实例给p,这是严重的错误!同时,这也不是单例的唯一实现!

    一、懒汉模式:即第一次调用该类实例的时候才产生一个新的该类实例,并在以后仅返回此实例。

    需要用锁,来保证其线程安全性:原因:多个线程可能进入判断是否已经存在实例的if语句,从而non thread safety.

    使用double-check来保证thread safety.但是如果处理大量数据时,该锁才成为严重的性能瓶颈。

     1、静态成员实例的懒汉模式:

    class Singleton
    {
    private:
        static Singleton* m_instance;
        Singleton(){}
    public:
        static Singleton* getInstance();
    };
    
    Singleton* Singleton::getInstance()
    {
        if(NULL == m_instance)
        {
            Lock();//借用其它类来实现,如boost
            if(NULL == m_instance)
            {
                m_instance = new Singleton;
            }
            UnLock();
        }
        return m_instance;
    }

    2、内部静态实例的懒汉模式

    这里需要注意的是,C++0X以后,要求编译器保证内部静态变量的线程安全性,可以不加锁。但C++ 0X以前,仍需要加锁。

    class SingletonInside
    {
    private:
        SingletonInside(){}
    public:
        static SingletonInside* getInstance()
        {
            Lock(); // not needed after C++0x
            static SingletonInside instance;
            UnLock(); // not needed after C++0x
            return &instance; 
        }
    };

    使用实例:

     1 #include<iostream>
     2 using namespace std;
     3 class CSingleton
     4 {
     5 private:
     6     CSingleton() {
     7     }
     8     ~CSingleton() {
     9         if (m_pInstance == NULL) {
    10             return;
    11         }
    12         delete m_pInstance;
    13         m_pInstance = NULL;
    14     }
    15     static CSingleton *m_pInstance;
    16 public:
    17     static CSingleton * GetInstance() {
    18         if(m_pInstance == NULL)
    19             m_pInstance = new CSingleton();
    20         return m_pInstance;
    21     }
    22 };
    23 CSingleton* CSingleton::m_pInstance = NULL;//类的静态成员变量需要在类外边初始化
    24 
    25 
    26 class SingletonInside
    27 {
    28 private:
    29     SingletonInside(){}
    30 public:
    31     static SingletonInside* getInstance()
    32     {
    33         static SingletonInside instance;
    34         return &instance; 
    35     }
    36     void init(){
    37         m_data = 10;
    38     }
    39     void set(){
    40         ++m_data;
    41     }
    42     int m_data;
    43 };
    44 
    45 int main() {
    46 
    47     SingletonInside* single1 = SingletonInside::getInstance();
    48     SingletonInside* single2 = SingletonInside::getInstance();
    49 
    50     single1->set();
    51     if (single1 == single2) {
    52         cout<<"Same"<<endl;
    53         single1->init();
    54         single1->set();
    55         cout<<single2->m_data <<endl;
    56     }else{
    57         cout<<"not =="<<endl;
    58     }
    59     return 0;
    60 }

    output:

    Same
    11

    二、饿汉模式:即无论是否调用该类的实例,在程序开始时就会产生一个该类的实例,并在以后仅返回此实例。

    由静态初始化实例保证其线程安全性,WHY?因为静态实例初始化在程序开始时进入主函数之前就由主线程以单线程方式完成了初始化,不必担心多线程问题。

    故在性能需求较高时,应使用这种模式,避免频繁的锁争夺。

    class SingletonStatic
    {
    private:
    static const SingletonStatic* m_instance;
    SingletonStatic(){}
    public:
    static const SingletonStatic* getInstance()
    {
    return m_instance;
    }
    };
    
    //外部初始化 before invoke main
    const SingletonStatic* SingletonStatic::m_instance = new SingletonStatic;

    特点与选择:

    由于要进行线程同步,所以在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能。这是以空间换时间。

    在访问量较小时,采用懒汉实现。这是以时间换空间。

  • 相关阅读:
    java概述------
    java中有几种方法实现一个线程?用什么关键字修饰同步方法?stop()和suspend()方法为何不推荐使用?
    java的5个框架,哪个框架更适合你的项目?
    java的热门应用有哪些?
    vue-router 切换页面时怎么设置过渡动画
    Referrer Policy 介绍
    await进行同步操作
    vue中axios拦截器同一项目多域名如何配置
    正规方程求解特征参数的推导过程
    一种网页中显示代码所涉及的字符转义问题的解决方案
  • 原文地址:https://www.cnblogs.com/guxuanqing/p/5876873.html
Copyright © 2011-2022 走看看