设计模式–单例模式
要搞清楚以下几个问题?
- 为什么要使用单例
- 单例存在哪些问题
- 单例与静态类的区别
- 有何替代的解决方案
什么是单例模式
单例设计模式:一个类只允许创建一个对象(实例),那这个类就是一个单例类,这种设计模式就是单例设计模式。
。。。。
实例1:避免i资源访问冲突
将对象级别的锁换成类级别的锁就行,让所有的对象都共享一把锁。这样就避免了不同对象之间同时调用log()函数,而导致的日志覆盖问题
单例模式的解决思路就简单一些了。单例模式相对于之前类级别锁的好处是,不用创建那么多Logger对象,一方面节省内存空间,另一方面节省系统文件句柄(对于OS来说,文件句柄也是一种资源,不能随便浪费)
我们将Logger设计成一个单例类,程序中只允许创建一个Logger对象,所有的线程共享使用这一个Logger对象,共享一个FileWriter对象,而FileWriter本身是对象级别线程安全的,也就避免了多线程情况下写日志会相互覆盖的问题。
1 | public class Logger{ |
实例2:表示全局唯一类
从业务概念上,如果有些数据在系统中只应保存一份,那么就比较适合设计为单例类
如:
- 配置信息类,
- 唯一递增ID号码生成器
如何实现一个单例
概括起来,要实现一个单例,我们需要关注的点无外乎下面几个
- 构造函数需要是private访问权限的,这样才能避免外部通过new创建实例
- 考虑对象创建时的线程安全问题
- 考虑是否支持延迟加载( 延迟加载的意思是说,在关联查询时,利用延迟加载,先加载主信息。使用关联信息时再去加载关联信息 )
- 考虑getInstance()性能是否高(是否加锁)
五种单例实现方法
饿汉式
饿汉式的实现方式比较简单,在类加载的时候,instance静态实例就已经创建并初始化好了,所以,instance实例的创建过程是线程安全的。不过这样的实现方式是不支持延迟加载的。
有人认为如果实例占用资源多(如内存)或者初始化耗时长(比如需要加载各种配置文件),提前初始化实例是一种浪费资源的行为。最好的方法应该是在用到的时候再去初始化
作者则认为:如果初始化耗时长,那最好不要等到真正要用到它的时候,采取执行这个耗时长的初始化过程,这会影响到系统的性能(如:在相应客户端接口请求的时候,做这个初始化操作,会导致此请求的响应时间变长,甚至超时);采用饿汉式实现方式,将耗时的初始化操作提前到程序启动的时候完成,这样就能避免程序运行的时候,再去初始化导致的性能问题
如果实例占用资源多,按照fail-fast的设计原则(有问题及早暴露),我们希望在程序启动时就将这个实例初始化好,如果资源不够,也会正在程序启动的时候触发报错,我们可以立即取修复
懒汉式
懒汉式对比饿汉式的优势是支持延迟加载
不过这种方式的并发度很低,类似串行
如果这个单例类是偶尔会被使用,这种设计是可以被接受的,但是如果被频繁使用,那么频繁加锁,释放锁及并发度低等操作,会导致性能瓶颈,这种实现方式不可取
双重检测
既支持延迟加载,又支持高并发的单例实现方法
主要instance被创建之后,再调用getInstance()函数都不会进入到加锁逻辑中,所以这种实现方式解决了懒汉式并发度低的问题
静态内部类
枚举
通过java枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性
单例存在哪些问题
- 单例对OOP特性支持不友好