单例模式与信号中心2
单例模式懒汉式实现的几种实现方式及其优缺点、最推荐的单例模式。
懒汉式
懒汉式:默认不会实例化,当需要的时候才会实例化。特点是延迟加载。
最常规的实现
1 |
|
- 只有当第一次调用
getInstance()
方法时,才会实例化该单例类; - 该实现方式只适用于单线程环境下,如果多个线程同时第一次执行
if (instance == nullptr)
判断instance
是否为空,此时所有的线程都会去实例化该单例,导致单例不再”单”;
该实现方法只可应用于单线程环境中。
加锁实现
1 |
|
- 同上1;
- 由于锁的存在,该实现方法可以应用于多线程环境中,不会出现上2的问题;
- 每次调用
getInstance()
函数都要进入临界区,消耗略大。
双检锁实现
能不能只在没有实例化单例类时加锁呢?双检锁实现就是这种思路。
1 |
|
- 只比加锁实现多了一行代码,就大大提高了效率。
- 该实现方法存在着一个严重的问题:构造过程不是原子操作,好巧不巧,在某些平台上会出现A线程正在②处执行构造,B线程来到①处判空,如果此时构造不完全,但是
instance
指针已被赋值,那么B线程会拿着一个未构造完全的实例使用。
遗憾的是C++11以前的版本对此无能为力。
《Head First 设计模式》中有提到在java
语言中,可以通过在声明变量时加volatile
关键词解决这个问题,其他地方类似于C++双检锁实现。
1 |
|
该书注释中提到,在1.4及更早版本的java
中,许多JVM
对于volatile
关键字的实现会导致双检锁的失效。
网上搜了以下,这是一个非常远古的版本。。。。。而我们的C++一直到C++11才解决该问题,orz。这难能可贵的重大更新,赶快”早买早享受”啊。
C++11实现
在C++11标准中,新增对于局部静态变量初始化具有线程安全性的要求。
Variables declared at block scope with the specifier
static
orthread_local
(since C++11) have static or thread (since C++11) storage duration but are initialized the first time control passes through their declaration (unless their initialization is zero- or constant-initialization, which can be performed before the block is first entered). On all further calls, the declaration is skipped.If multiple threads attempt to initialize the same static local variable concurrently, the initialization occurs exactly once (similar behavior can be obtained for arbitrary functions with std::call_once).
Note: usual implementations of this feature use variants of the double-checked locking pattern, which reduces runtime overhead for already-initialized local statics to a single non-atomic boolean comparison.
所以可以借助该特性,采用如下的方式实现单例类:
1 |
|
- 只有当第一次调用
getInstance()
方法时,才会实例化该单例类 - 如果多个线程试图同时初始化同一静态局部变量,则初始化严格发生一次,所以该实现是线程安全的,颇费~
其他实现可以围观这位大佬的博客,真是大开眼界C++程序员们,快来写最简洁的单例模式吧 - 老司机 - 博客园 (cnblogs.com),也侧面反映了C++的难度。。。。
参考
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!