1 单例模式概念与特点
单例模式的概念:
单例类是创建型设计模式的一种,它的核心思想是保证一个类只有一个实例,并提供一个全局访问点来访问这个实例。
单例模式的特点:
- 一个类只有一个实例
- 提供一个访问该实例的全局访问点
常见的单例模式实现方式:饿汉模式、懒汉模式、双重锁定检查、静态内部类、枚举
2 使用原因
- 全局控制:保证全局只有一个实例,这样可以严格控制客户怎样去访问它以及何时访问它,也就是对唯一的实例进行受控访问。
- 节省资源:因为只有一个实例的存在,避免了创建相同的对象,从而节省了系统资源,而且多个模块可以通过单例实现共享
- 懒加载:单例模式还可以实现懒加载,只有在需要的时候才进行了实例化,这会提高程序的性能(主要体现在程序的启动、用户的体验与响应、资源的按需分配、延迟计算提升速度)。
3 代码实现
主要实现三种单例模式:饿汉模式、懒汉模式、双重锁定检查模式
3.1 单例模式实现的基本规则
- 私有的构造函数:防止从外部进行创建实例(可以)
- 私有的静态实例变量:确保只有一个实例
- 共有的静态方法:提供获取实例的全局访问点
3.2 饿汉模式
在程序开始时就创建好实例进行资源初始化,或者说类加载时就已经完成了实例的创建,不管后面创建的实例有没有使用,先创建再说。
测试:
输出:
构造函数因为在单例类一开始就已经在main函数外声明初始化,所以先调用了构造函数,后续的测试证明为同一个实例对象。
3.3 懒汉模式
在程序需要的时候再进行实例化对象,单例类的声明都是相同的,差别仅在于实例的资源申请时间。
输出:
注意因为懒汉模式是在检测到没有实例的时候进行创建,此时若是在多线程的环境下,多个线程同时检测到实例不存在,然后同时进行创建就会创建多个实例,所以懒汉模式并不是线程安全的,需要进一步引入同步机制(如互斥锁)来进行约束。饿汉模式的性能开销主要体现在创建时,资源初始化,所以在程序启动时速度会收到影响,但是它天然线程安全所以在运行过程不需要同步机制的开销,而饿汉模式在程序启动时并不会影响程序,但性能的开销主要体现在同步的机制上,可以通过双重检查锁来提高性能。
3.4 双重检查锁定
双重检查锁定(DCLP, Double-Checked Locking Pattern)是一种常用的技术,特别是在实现单例模式时。它旨在减少同步开销,确保在多线程环境中安全地创建和访问单例实例。双重检查锁定特别适用于懒加载(lazy initialization),即仅在首次需要时创建实例。
双重检查锁定的要点是进行两次检查:
- 在进入同步块(锁)之前进行第一次检查。
- 在进入同步块(锁)之后再进行一次检查。
这种机制可以确保初始化只有一次,并且在创建实例后并发访问不会频繁触发同步开销。
第一次检查锁是为了加速,检查到已经有实例了就直接返回,第二次检查是为了避免多次创建实例,具体的原因如下:
也可以使用std::call_once来进行懒汉模式的实现:
std::call_once的功能是保证代码块只执行一次(无论多少线程访问它),需要配合std::once_flag(标志,跟踪代码段是否执行过)进行使用。
3.5 具体应用场景
- 资源共享
多个模块进行共享某个资源的时候考虑使用单例模式,比如程序中需要用一个全局存储和管理配置信息或者是数据库连接池等。 - 只有一个实例
当系统中某个类只需要一个实例来协调行为的时候,可以考虑使用单例模式, 比如说管理应用程序中的缓存,确保只有一个缓存实例,避免重复的缓存创建和管理,或者使用单例模式来创建和管理线程池。 - 懒加载
如果对象创建本身就比较消耗资源,而且整个应用程序中都不一定使用,则可以使用懒汉模式来实现懒加载。
实例:
Java中的Runtime类就是比较经典的单例类,用来表示程序运行时环境。Spring框架中的程序上下文类ApplicationContext也被设计为单例类。