单例模式
提出问题
- 有些对象其实我们只需要一个。如果制造出多个,就会导致许多问题的产生。
- 如线程池、缓存、对话框、处理偏好设置和注册表的对象、日志对象,充当打印机、显卡设备的驱动程序的对象。
- 例如配置文件对象,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。
- 使用全局变量存在缺陷。
- 如果使用全局变量,则必须在程序一开始就创建好对象。如果对象非常消耗资源,而程序在这次执行中又没有使用到它,则形成浪费。
概述
是什么
单例模式:确保一个类只有一个实例,并提供一个全局访问点
分类
应用
适用性
- 当类只能有一个实例,而且客户可以从一个众所周知的访问点访问它。
- 当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例。
- 需要频繁实例化然后销毁的对象。
- 创建对象时耗时过多或者耗资源过多,但又经常用到的对象
- 有状态的工具类对象。
- 频繁访问数据库或文件的对象。
案例
资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
控制资源的情况下,方便资源之间的互相通信。如线程池等。
协作
结构
参与者
协作
- 类关系
- 逻辑关系
效果
- 对唯一实例的受控访问。因为Singleton类封装它的唯一实例,所以可以严格控制客户怎样以及何时访问它。
- 缩小名空间。Singleton是对全局变量的一种改进,避免了那些存储唯一实例的全局变量污染名空间。
- 允许对操作和表示的精化。Singleton可以有子类,而且用这个扩展类的。
- 允许可变数目的实例。
- 比类操作更为灵活。
权衡
基础
优缺
- 为什么全局变量比单例差:
- 急切实例化VS延迟实例化。
- 全局变量可以提供全局访问,但是不能保证只有一个实例。而且全局变量指向很多小对象会造成命名空间的污染。
- 为什么不创建一个类,把所有的方法和变量定义为静态,将类当做一个单例:
- 如果类自给自足,而且不依赖于复杂的初始化,那么OK的。
- 但静态初始化的控制权是在Java手上,并且当很多类牵扯其中时,就可能有一些与初始化次序有关的bug。
- 由于在系统内存中只存在一个对象,因此可以 节约系统资源,当 需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
- 避免对共享资源的多重占用。
实现
懒汉式
线程不安全法
1 | /** |
双重检测机制(线程不安全)
1 | /** |
线程安全法
不推荐法
1 | public class SingletonExample3 { |
双重同步锁
基于volatile
1 | /** |
饿汉模式
通过静态域实现
1 | /** |
通过静态块实现
1 |
|
枚举模式
1 | /** |
底层原理
与其他的区别
设计思想
进阶
- 为什么不直接使用synchronized同步getInstance(),简单有效。
- 因为同步一个方法可能导致程序的执行效率下降100倍,如果这个getInstance()在一个频繁运行的地方,那性能将很差了。
- 两个类加载器有机会各自创建自己的单例。
- 自行指定类加载器,指定同一个类加载器。