一、单例设计模式的定义
一个类只允许创建一个对象(或叫实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式(Singleton Pattern)。
二、单例的用处
从业务概念上,有些数据在系统中只应保存一份,即使用单例模式:
-
处理资源访问冲突:
打印日志 Logger 类 ,所有的日志都写入到同一个文件中,若分别创建多个Logger对象,多线程会可能出现同时写入相互覆盖的情况。FileWriter 本身是线程安全的,内部实现本身就加了对象级别的锁,所以只能给其加类级别的锁才有用。
单例模式相对于之前类级别锁的好处是:不用创建那么多的Logger对象,一方面节省内存空间,另一方面节省系统文件句柄(对于操作系统来说,文件句柄也是一种资源,不能随便浪费)
-
表示全局唯一类:
如配置信息类,在系统中,只有一个配置文件,当配置文件被加载到内存之后,以对象的形式存在,也理所当然只有一份。
三、如何实现一个单例
- 构造函数需要是private访问权限的,才能避免外部通过new创建实例。
- 考虑对象创建时的线程安全问题。
- 考虑是否支持延迟加载
- 考虑getInstance()性能是否高(是否加锁)。
四、实现方式
-
饿汉式
饿汉式的实现方式,在类加载的期间,就已经将instance静态实例初始化好了,所以instance实例的创建是线程安全的。
但是不支持延迟加载实例。采用饿汉式实现方式,将耗时的初始化操作,提前到程序启动的时候,这样就能避免在程序运行的时候,再去初始化,导致性能问题。
如果实例占用资源多,按照fail-fast设计原则(有问题及早暴露),那么我们也希望在程序启动的时候就触发报错,可以立即修复,可以避免在程序运行一段时间后,因实例占用资源过多导致系统崩溃,影响系统的可用性。
-
懒汉式
懒汉式相对于饿汉式的优势是支持延迟加载。
其缺点是会导致频繁加锁,释放锁,以及并发度低等问题,频繁的调用产生性能瓶颈。 -
双重检测
既支持延迟加载,又支持高并发的单例实现方式。只要instance被创建之后,在调用getInstance()函数都不会进入到加锁逻辑中。
-
静态内部类
比双重检测简单。
-
枚举
基于枚举类型的单例实现,通过Java枚举类型本身的特性,保证了实例创建的线程安全和实例的唯一性。
五、单例存在哪些问题
- 单例对OOP特性的支持不友好。(意味着放弃继承和多态的特性)
- 单例会隐藏类之间的依赖关系。(单例类不需要显示创建,不需要依赖参数传递,只有仔细查看每个函数的代码实现,才能知道依赖了哪些单例类)
- 单例对代码的可测试下不友好,对代码的扩展性不友好。
- 单例对代码的可测试性不友好,对代码的扩展性不友好。单例类只能有一个对象实例,如果某一天需要创建多个实例或多个实例对象,代码改动比较大(如数据库连接池、线程池的资源池最好不用单例)
- 单例不支持有参数的构造函数。
六、单例有什么替代解决方案
为了保证全局唯一性,除了使用单例,还可以用静态方法实现,不过静态方法这种思想比单例更加不灵活,比如,它无法支持延迟加载,如果需要完全解决这些问题,可能要从根上寻找其他方式来实现全局唯一类,如:通过工厂模式、IOC容器(Spring IOC容器)来保证,由程序员自己来保证不创建两个对象。
有人把单例当作反模式,主张杜绝在项目中使用,思想有点极端,模式没错,关键是怎么用,如果单例类并没有后续扩展的需求,并且不依赖外部系统,那设计成单例类就没有太大问题。
七、如何理解单例模式的唯一性
单例类中对象的唯一性的作用范围是“进程唯一”的。
- 进程唯一:指的是进程内唯一,进程间不唯一;意味着线程内、线程间都唯一;
- 线程唯一:指的是线程内唯一,线程间不唯一;
- 集群唯一:指的是进程内、进程间也唯一。
八、如何实现线程唯一的单例、集群环境下的单例
-
实现线程唯一的单例
通过一个HashMap来存储对象,其中key是线程ID,value是对象,就可以做到不同的线程对应不同的对象,同一个线程只能对应一个对象,实际上,Java语言本身提供了ThreadLocal并发工具类,可以更加轻松的实现线程唯一单例。
-
集群环境下的单例
集群唯一:指的是进程内、进程间也唯一。即不同的进程间共享同一个对象,不能创建同一个类的多个对象。
我们需要把这个单例对象序列化并存储到外部共享存储区(如文件)。进程在使用这个单例对象的时候,需要先从外部共享存储区中将它读取到内存中,并反序列化成对象,然后再使用,使用完成之后还需要再存储回外部共享存储区。为了保证任何时刻在进程间都只有一份对象存在,一个进程在获取到对象之后,需要对对象加锁,避免其他进程再将其获取。再进程使用完这个对象之后,需要显示的将对象从内存中删除并且释放对对象的加锁。
九、如何实现一个多例模式
“单例”指的是一个类只能创建一个类,对应地,“多例”指的是一个类可以创建多个对象,但是个数是有限制的,比如只能创建3个对象。多例的实现也比较简单,通过一个Map来存储对象类型和对象之间的对应关系,来控制这个对象。