设计模式(二):单例模式

一.定义

        保证一个类仅有一个实例,并提供一个该实例的全局访问点。

二.类图

 三.案例

分享几个常见的错误案例并且分析错误原因:

版本1.

class Singleton {
 public:
  static Singleton *Instance() {
    if (instance == nullptr) {
      instance = new Singleton;
    }  
    return instance;
  }
  
  ~Singleton() = default;
  
 private:
  Singleton() = default;
  Singleton(const Singleton &other) = default;
  Singleton& operator=(const Singleton &other) = default;
  
  static Singleton *instance;
};

Singleton *Singleton::instance = nullptr;

版本1有两个问题:

1. 单线程存在内存泄漏问题(instance指针作为static对象,在程序结束时会释放。但是instance指针所指对象存在于堆上)

2. 多线程存在线程安全问题

版本2.

class Singleton {
 public:
  static Singleton *Instance() {
    if (instance == nullptr) {
      std::lock_guard<std::mutex> lock(_mutex);
      if (instance == nullptr) {
        instance = new Singleton;
        // 结束时自动调用
        atexit(Destroy);
      }
    }
    return instance;
  }
  
  ~Singleton() = default;

 private:
  Singleton() = default;
  Singleton(const Singleton &other) = default;
  Singleton& operator=(const Singleton &other) = default;

  // 销毁
  static void Destroy() {
    delete instance;
    instance = nullptr;
  }

  static Singleton *instance;
  // 互斥锁
  static std::mutex _mutex;
};

Singleton *Singleton::instance = nullptr;

版本2解决了单线程情况下的释放问题,在atexit()中注册销毁函数,在程序结束时自动调用销毁函数对堆区进行释放。

版本2还通过使用互斥锁试图解决线程安全问题,但是实际上版本2依然存在线程安全问题。

在计算机底层中,new操作会被拆分为3个原子操作,分别是:(1)分配内存 (2)调用构造函数 (3)给指针(instance)赋值        

但是由于可能出现指令reorder,导致原本1,2,3顺序变为1,3,2顺序,此时单例模式发生问题。在线程1中,指令执行到3之后进行线程切换,此时线程2执行第一个if语句发现instance并不为空,于是返回。但此时instance所指内存有可能为随机值,最终导致发生coredump。

正确的方法:

版本1.

class Singleton {
 public:
  static Singleton *Instance() {
    static Singleton instance;
    return &instance;
  }

  ~Singleton() = default;

 private:
  Singleton() = default;
  Singleton(const Singleton &other) = default;
  Singleton& operator=(const Singleton &other) = default;
};

C++11提供了magic static特性,此特性可以保证变量在初始化时,并发进入声明语句,并发线程将会阻塞等待初始化结束,这样保证了线程安全。同时因为instance对象为static类型,所以会自动释放。

版本2.

class Singleton {
 public:
  static Singleton *Instance() {
    if (instance == nullptr) {
      pthread_once_t once = PTHREAD_ONCE_INIT;
      pthread_once(&once, init);
    }
    return instance;
  }

  ~Singleton() = default;

 private:
  Singleton() = default;
  Singleton(const Singleton &other) = default;
  Singleton& operator=(const Singleton &other) = default;
  
  static Singleton *instance;
  
  static void init() {
    instance = new Singleton;
    atexit(destroy);
  }
  
  static void destroy() {
    delete instance;
    instance = nullptr;
  }
};

Singleton *Singleton::instance = nullptr;

若没有magic static特性可以使用系统调用pthread_once()解决线程安全问题。 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值