一、定义
保证一个类仅有一个实例,并提供一个该实例的全局访问点。
二、结构
三、创建方式
1 2 3 4 5 6 7 8
| class Singleton { private: Singleton(); Singleton(const Singleton& other); public: static Singleton* getInstance(); static Singleton* m_instance; };
|
1、线程非安全版本
1 2 3 4 5 6 7
| Singleton *Singleton::m_instance = nullptr; Singleton* Singleton::getInstance(){ if(m_instance == nullptr) { m_instance = new Singleton(); } return m_instance; }
|
- 存在问题:如果有A、B两个线程,线程A首次进来判断if(第二行),但此时时间片用完了,轮到B执行if(第二行),这时候A、B线程会创建对象,会出现创建2个对象的情况。
- 适用场景:单线程
2、线程安全版本,但所的代价过高
1 2 3 4 5 6 7 8
| Singleton *Singleton::m_instance = nullptr; Singleton* Singleton::getInstance(){ Lock lock; if(m_instance == nullptr) { m_instance = new Singleton(); } return m_instance; }
|
- 存在问题:n个线程的读操作是不需要加锁,对于读操作,加锁是浪费的。在高并发情景不合适。
3、双检查锁版本,但由于内存读写reorder不安全
1 2 3 4 5 6 7 8 9 10
| Singleton *Singleton::m_instance = nullptr; Singleton* Singleton::getInstance(){ if(m_instance == nullptr) { Lock lock; if(m_instance == nullptr) { m_instance = new Singleton(); } } return m_instance; }
|
用于解决A、B两个线程同时进入到第三行,再检查一次,如果为空才创建
一段代码有指令序列,我们以为它会在假想的顺序执行:(第5行)
1、先分配一个内存;
2、调用Singleton的构造器,对刚才分配的内存做初始化;
3、将创建出来指针,即对象真正的内存地址,赋值给m_instance。
- 存在问题:线程是在指令层次抢时间片,但真正在cpu指令级别的情况,三个步骤不一定按顺序(reorder),有时候实际情况和我们假想的顺序不一样。(如:1,3,2)
- 如果出现reorder, 这时候A线程创建了对象(实际是原生的一块内存,没有执行构造器)。随后B线程进来直接返回该对象,但其实这个对象是不可用的。
- 在所有编译器中,如果不加volatile会出问题,并且出问题的概率很高。
- 通常是由编译器来优化reoder的问题 。
Go版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| package main
import ( "log" "sync" "time" )
type Singleton struct { Name string }
var ( instance *Singleton lock sync.Mutex )
func GetInstance() *Singleton { if instance == nil { lock.Lock() defer lock.Unlock() if instance == nil { instance = &Singleton{Name: "zhangsan"} } } return instance }
func main() { for i := 0; i < 5; i++ { go func() { resp := GetInstance() log.Printf("the value is : %s", resp.Name) }() } time.Sleep(time.Second) }
|
4、双检查锁的正确实现(volatile)
C++11版本之后的跨平台实现(volatile),屏蔽编译器的reorder
四、使用场景
- 由于性能问题,只能创建一个实例的场景,如数据连接的对象,只需要创建一次,后续对数据库的操作可以复用这个对象。