单例模式(Singleton),也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理
在单例模式中,将构造函数设为private, 无法通过new创建对象;
好在类的静态成员函数不需要创建对象即可运行,而静态成员函数又属于类的一部分,这样就可以调用构造函数构造对象了,并且通过对静态成员变量的判断,确保了该类只能通过静态成员函数生成唯一的一个对象,这就是单例模式的原理.
单例模式构建要素:
1.私有的构造函数;
- 静态的自身类引用;
- 提供静态的外部接口;
单例模式应用场景:
(1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如日志文件,应用配置。
(2)控制资源的情况下,方便资源之间的互相通信。如线程池等。
单例模式应用实例:
- Windows的Task Manager(任务管理器)是很典型的单例模式;
- windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
- 网站的计数器,一般也是采用单例模式实现,否则难以同步。
- 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
- Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
- 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
- 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
- HttpApplication 也是单位例的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一个HttpApplication实例.
优点
1.在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就 防止其它对象对自己的实例化,确保所有的对象都访问一个实例
2.单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
3.提供了对唯一实例的受控访问。
4.由于在系统内存中只存在一个对象,因此可以 节约系统资源,当 需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
5.允许可变数目的实例。
6.避免对共享资源的多重占用。
缺点
1.不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
2.由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
3.单例类的职责过重,在一定程度上违背了“单一职责原则”。
4.滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
原始版
public class Singleton {
private:
Singleton() {} //私有构造函数
static Singleton instance = nullptr; //单例对象
//静态工厂方法
public:
static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
代码解释:
1.要想让一个类只能构建一个对象,自然不能让它随便去做new操作,因此Signleton的构造方法是私有的。
2.instance是Singleton类的静态成员,也是我们的单例对象。它的初始值可以写成Null,也可以写成new Singleton()。至于其中的区别后来会做解释。
3.getInstance是获取单例对象的方法。
如果单例初始值是null,还未构建,则构建单例对象并返回。这个写法属于单例模式当中的懒汉模式。
如果单例对象一开始就被new Singleton()主动构建,则不再需要判空操作,这种写法属于饿汉模式.
这两个名字很形象:饿汉主动找食物吃,懒汉躺在地上等着人喂。
添加线程安全
原始代码线程不安全,因为在Singleton类刚刚被初始化, instance对象是空,如果两个线程同时访问getInstance方法,两个条件都通过了条件判断,开始new操作.
经典线程安全懒汉模式
1class single{
2private:
3 //私有静态指针变量指向唯一实例
4 static single *p;
5
6 //静态锁,是由于静态函数只能访问静态成员
7 static pthread_mutex_t lock;
8
9 //私有化构造函数
10 single(){
11 pthread_mutex_init(&lock, NULL);
12 }
13 ~single(){}
14
15public:
16 //公有静态方法获取实例
17 static single* getinstance();
18
19};
20
21pthread_mutex_t single::lock;
22
23single* single::p = NULL;
24single* single::getinstance(){
25 if (NULL == p){
26 pthread_mutex_lock(&lock);
27 if (NULL == p){
28 p = new single;
29 }
30 pthread_mutex_unlock(&lock);
31 }
32 return p;
33}
单例模式的实现思路如前述所示,其中,经典的线程安全懒汉模式,使用双检测锁模式。
如果只检测一次,在每次调用获取实例的方法时,都需要加锁,这将严重影响程序性能。双层检测可以有效避免这种情况,仅在第一次创建单例的时候加锁,其他时候都不再符合NULL == p的情况,直接返回已创建好的实例。
饿汉模式
饿汉模式不需要用锁,就可以实现线程安全。原因在于,在程序运行时就定义了对象,并对其初始化。之后,不管哪个线程调用成员函数getinstance(),都只不过是返回一个对象的指针而已。所以是线程安全的,不需要在获取实例的成员函数中加锁。
1class single{
2private:
3 static single* p;
4 single(){}
5 ~single(){}
6
7public:
8 static single* getinstance();
9
10};
11single* single::p = new single();
12single* single::getinstance(){
13 return p;
14}
15
16//测试方法
17int main(){
18
19 single *p1 = single::getinstance();
20 single *p2 = single::getinstance();
21
22 if (p1 == p2)
23 cout << "same" << endl;
24
25 system("pause");
26 return 0;
27}
饿汉模式虽好,但其存在隐藏的问题,在于非静态对象(函数外的static对象)在不同编译单元中的初始化顺序是未定义的。如果在初始化完成之前调用 getInstance() 方法会返回一个未定义的实例。
什么时候使用单例什么时候使用静态类?
单例:
单例模式比静态方法有很多优势:
首先,单例可以继承类,实现接口,而静态类不能(可以集成类,但不能集成实例成员);
其次,单例可以被延迟初始化,静态类一般在第一次加载是初始化;
再次,单例类可以被集成,他的方法可以被覆写;
最后,或许最重要的是,单例类可以被用于多态而无需强迫用户只假定唯一的实例。举个例子,你可能在开始时只写一个配置,但是以后你可能需要支持超过一个配置集,或者可能需要允许用户从外部从外部文件中加载一个配置对象,或者编写自己的。你的代码不需要关注全局的状态,因此你的代码会更加灵活
静态方法
静态数据成员是类的成员,而不是对象的成员,所有该类对象都共用该数据成员,可以实现同类对象之间进行数据共享。
静态数据成员是静态存储的,它是静态生存期,必须对它进行初始化
静态方法中产生的对象,会随着静态方法执行完毕而释放掉,而且执行类中的静态方法时,不会实例化静态方法所在的类。如果是用singleton, 产生的那一个唯一的实例,会一直在内存中,不会被GC清除的(原因是静态的属性变量不会被GC清除),除非整个JVM退出了。这个问题我之前也想几天,并且自己写代码来做了个实验。
1、静态数据成员在定义或声明时前面加关键字static。
eg: static CSingleton *p;
2、静态变量的初始化f方式:
<数据类型><类名>::<静态数据成员名>=<值>
eg: CSingleton* CSingleton::p = NULL;
初始化在类外实现,不能在头文件中,前面不加static,需要使用作用域运算符来标明它所属类;
3、类外引用静态数据成员时,采用如下格式:
<类名>::<静态成员名>
如果你需要维护一些数据或者状态,那么使用单例模式。静态类实是放一些简单的常量或者无状态的函数,例如技巧篇(http://blog.csdn.net/candycat1992/article/details/24884667#t1)这里的技巧1和技巧2。
从上面的代码里你可以看出来,单例模式使用的类实际上就是一个普通的类。因此你可以把它们当成参数传递给其他脚本。
单例模式可以延迟类的初始化。从代码里你可以看出,只有需要的时候才会实例化那个单例。而静态类在加载的时候就被绑定了。
单例具有很好的面向对象的特性。这句话比较虚,我的理解是它的可变性很大,可以继承可以重载等等等等。