一、背景

单例模式主要是为了避免因为创建了多个实例造成资源的浪费,且多个实例由于多次调用容易导致结果出现错误,而使用单例模式能够保证整个应用中有且只有一个实例。从其名字中我们就可以看出所谓单例,就是单个实例也就是说它可以解决的问题是:可以保证一个类在内存中的对象的唯一性,在一些常用的工具类、线程池、缓存,数据库,账户登录系统、配置文件等程序中可能只允许我们创建一个对象,一方面如果创建多个对象可能引起程序的错误,另一方面创建多个对象也造成资源的浪费。

二、模式定义

保证一个类仅有一个实例,并提供一个访问它的全局访问点。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式UML比较简单,只有一个单例类:

Singleton
-static uniqueInstance
+static getInstance

三、单例模式设计要点

(1)类必须保证在任何时刻只有一个实例存在 —>类的静态成员在整个类中只存在一个
(2)构造函数为私有:private Singleton(){ } —>不允许用户自行实例化类的实例
(3)有一个静态方法的getInstance()和一个静态的私有成员变量uniqueInstance
(4)单例模式需要注意线程安全

四、实例化方式

急切实例化-饿汉式:在类加载的时候,通过直接给静态成员变量uniqueInstance创建对象–线程安全
延迟实例化-懒汉式:在首次需要的时候才创建对象

五、单例模式线程安全讨论

单例模式的核心在于:该类只能实例化一个对象,不允实例化两个对象。2个线程首次同时调用getInstance()获取实例,可能产生2个实例对象,解决方法就是给getInstance()加锁或者是采取饿汉式单例模式。

1. 延迟实例化(懒汉式-线程不安全)
C++设计模式(6):单例模式
优点:懒加载,启动速度快、如果从始至终都没使用过这个实例,则不会初始化该实例,可节约资源。

缺点:多线程环境下线程不安全。if (uniqueInstance == NULL) 存在竞态条件,可能会有多个线程同时进入 if 语句,导致产生多个实例。

2.延迟实例化(懒汉式-线程安全)
C++设计模式(6):单例模式
优点:使得原始懒汉式变得线程安全。

缺点:加锁操作只在实例第一次实例化的时候有用,但是自实例化后,后续99%的情况下,加锁操作都是多余的,每次只有一个线程可以进去该方法获取实例,并发性能极差。

3.延迟实例化(懒汉式-双重检查线程安全)

C++设计模式(6):单例模式
优点:第一次实例化的时候,加锁操作保证了线程安全,实例化完成后,当再次获取实例对象的时候,由于最外层的if判断,保证了不再会进行加锁操作,提高了(2)中的效率且保证了线程安全

缺点:每次获取实例,都要进行多余的if语句判断,但是影响不是很大。

4.饿汉式实例化
C++设计模式(6):单例模式
优点:简单,使用时没有延迟;在类装载时就完成实例化,天生的线程安全。

缺点:没有懒加载,启动较慢;如果从始至终都没使用过这个实例,则会造成系统启动时候的内存浪费。

六、单例模式总结

1.适用场景
(1)系统只需要一个实例对象,如系统要求提供一个唯一的***生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。
(2)客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。

2. 优点
(1)单例模式提供了对唯一实例的受控全局访问
(2)由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式可以提高系统的性能。
(3)允许可变数目的实例。基于单例模式我们可以进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例,既节省系统资源,又解决了单例单例对象共享过多有损性能的问题。

3.缺点
(1)由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
(2)单例类的职责过重,在一定程度上违背了 “单一职责原则”。
(3)如果实例化的共享对象长时间不被利用,系统可能会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致共享的单例对象状态的丢失。

相关文章: