【设计模式】单例模式 Singleton Pattern
单例模式,或称作单件模式,在整个应用程序运行中只有一个实例并提供一个全局访问点。
用途:整个程序只需要一个实例,eg.线程池、缓存、注册表、日志对象、打印机驱动等。
如何实现单一实例,
1)定义构造函数为private,禁止外部构造实例。
2)提供static的Instance函数自己提供的实例。之所以定义为static是因为构造函数为private不能通过外部创建的实例访问,只能通过静态类方法调用Singleton::Instance()。而static本身也符合唯一实例的思想。
C++实现1:
Singleton.h
#ifndef _SINGLETON_H_ #define _SINGLETON_H_ #include <iostream> class Singleton { public: static Singleton* Instance(); private: Singleton(); static Singleton* _instance; }; #endif
Singleton.cpp
#include "Singleton.h" #include "stdio.h" Singleton* Singleton::_instance = NULL; Singleton* Singleton::Instance() { if (_instance == NULL) { _instance = new Singleton(); } return _instance; } Singleton::Singleton() { //todo something }
main.cpp
#include "Singleton.h" int main(int argc,char* argv[]) { Singleton* instance = Singleton::Instance(); //todo something return 0; }
C++实现1问题:以上代码未考虑多线程:
1)线程1线程2先后进入函数走到(几乎是同时)
if (instance == NULL)
2)线程1创建实例退出函数。
3)线程2已经通过instance == NULL的判断,也创建实例。
此时就创建出了2个实例,这明显违背了单一实例的原则。
C++实现2:Lazy的实现
Singleton.h
#ifndef _SINGLETON_H_ #define _SINGLETON_H_ #include <iostream> class Singleton { public: static Singleton* Instance(); private: Singleton(); static Singleton* _instance; }; #endif
Singleton.cpp
#include "Singleton.h" #include "stdio.h" Singleton* Singleton::_instance = new Singleton(); Singleton* Singleton::Instance() { return _instance; } Singleton::Singleton() { //todo something }
C++实现2问题:
此种实现在进程启动时就创建出单一实例,后面不判断直接使用。但是如果Singleton存储数据很多,而应用场景很少,比如系统从开始到结束从来被调用过单例,那么这种不管三七二十一就创建的方式对系统资源是一个浪费。
C++实现3:考虑多线程
Singleton.cpp
#include "Singleton.h" #include "stdio.h" Singleton* Singleton::_instance = NULL; Singleton* Singleton::Instance() { if (_instance == NULL) { mutex; //加锁-伪代码 if (_instance == NULL) { _instance = new Singleton(); } mutex; //放锁-伪代码 } return _instance; } Singleton::Singleton() { //todo something }
为什么要2次判空。
如果一个加锁,一个判空:
1)先加锁后判空,则每次调用Singleton::Instance()都要加锁,而只有第一次创建实例的时候才有可能重复创建实例,以后每次都加锁大大浪费性能。
2)先判空后加锁,与之前没有锁是一样的,达不到防止重入的效果。
需要注意的是,访问(增删查)单例类的数据时依然需要加锁。
因为可能存在增删查同时进行的情况,而C++是通过迭代器访问数据,所以例如在线程1读的时候,线程2对数据进行了增删,则线程1的迭代器一经失效,可能因为程序崩溃。
C#实现请参考汤姆大叔的神作:
大叔手记(10):别再让面试官问你单例(暨6种实现方式让你堵住面试官的嘴)
标准版实现:
1 public sealed class Singleton 2 { 3 // 依然是静态自动hold实例 4 private static volatile Singleton instance = null; 5 // Lock对象,线程安全所用 6 private static object syncRoot = new Object(); 7 8 private Singleton() { } 9 10 public static Singleton Instance 11 { 12 get 13 { 14 if (instance == null) 15 { 16 lock (syncRoot) 17 { 18 if (instance == null) 19 instance = new Singleton(); 20 } 21 } 22 23 return instance; 24 } 25 } 26 }