设计模式——创建型:单例模式

一、单例设计模式的定义

一个类只允许创建一个对象(或叫实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式(Singleton Pattern)。

二、单例的用处

从业务概念上,有些数据在系统中只应保存一份,即使用单例模式:

  • 处理资源访问冲突:

    打印日志 Logger 类 ,所有的日志都写入到同一个文件中,若分别创建多个Logger对象,多线程会可能出现同时写入相互覆盖的情况。FileWriter 本身是线程安全的,内部实现本身就加了对象级别的锁,所以只能给其加类级别的锁才有用。

    单例模式相对于之前类级别锁的好处是:不用创建那么多的Logger对象,一方面节省内存空间,另一方面节省系统文件句柄(对于操作系统来说,文件句柄也是一种资源,不能随便浪费)

  • 表示全局唯一类:

    如配置信息类,在系统中,只有一个配置文件,当配置文件被加载到内存之后,以对象的形式存在,也理所当然只有一份。

三、如何实现一个单例

  1. 构造函数需要是private访问权限的,才能避免外部通过new创建实例。
  2. 考虑对象创建时的线程安全问题。
  3. 考虑是否支持延迟加载
  4. 考虑getInstance()性能是否高(是否加锁)。

四、实现方式

  1. 饿汉式

    饿汉式的实现方式,在类加载的期间,就已经将instance静态实例初始化好了,所以instance实例的创建是线程安全的。

    但是不支持延迟加载实例。采用饿汉式实现方式,将耗时的初始化操作,提前到程序启动的时候,这样就能避免在程序运行的时候,再去初始化,导致性能问题。

    如果实例占用资源多,按照fail-fast设计原则(有问题及早暴露),那么我们也希望在程序启动的时候就触发报错,可以立即修复,可以避免在程序运行一段时间后,因实例占用资源过多导致系统崩溃,影响系统的可用性。

  2. 懒汉式

    懒汉式相对于饿汉式的优势是支持延迟加载。
    其缺点是会导致频繁加锁,释放锁,以及并发度低等问题,频繁的调用产生性能瓶颈。

  3. 双重检测

    既支持延迟加载,又支持高并发的单例实现方式。只要instance被创建之后,在调用getInstance()函数都不会进入到加锁逻辑中。

  4. 静态内部类

    比双重检测简单。

  5. 枚举

    基于枚举类型的单例实现,通过Java枚举类型本身的特性,保证了实例创建的线程安全和实例的唯一性。

五、单例存在哪些问题

  • 单例对OOP特性的支持不友好。(意味着放弃继承和多态的特性)
  • 单例会隐藏类之间的依赖关系。(单例类不需要显示创建,不需要依赖参数传递,只有仔细查看每个函数的代码实现,才能知道依赖了哪些单例类)
  • 单例对代码的可测试下不友好,对代码的扩展性不友好。
  • 单例对代码的可测试性不友好,对代码的扩展性不友好。单例类只能有一个对象实例,如果某一天需要创建多个实例或多个实例对象,代码改动比较大(如数据库连接池、线程池的资源池最好不用单例)
  • 单例不支持有参数的构造函数。

六、单例有什么替代解决方案

为了保证全局唯一性,除了使用单例,还可以用静态方法实现,不过静态方法这种思想比单例更加不灵活,比如,它无法支持延迟加载,如果需要完全解决这些问题,可能要从根上寻找其他方式来实现全局唯一类,如:通过工厂模式、IOC容器(Spring IOC容器)来保证,由程序员自己来保证不创建两个对象。

有人把单例当作反模式,主张杜绝在项目中使用,思想有点极端,模式没错,关键是怎么用,如果单例类并没有后续扩展的需求,并且不依赖外部系统,那设计成单例类就没有太大问题。

七、如何理解单例模式的唯一性

单例类中对象的唯一性的作用范围是“进程唯一”的。

  • 进程唯一:指的是进程内唯一,进程间不唯一;意味着线程内、线程间都唯一;
  • 线程唯一:指的是线程内唯一,线程间不唯一;
  • 集群唯一:指的是进程内、进程间也唯一。

八、如何实现线程唯一的单例、集群环境下的单例

  1. 实现线程唯一的单例

    通过一个HashMap来存储对象,其中key是线程ID,value是对象,就可以做到不同的线程对应不同的对象,同一个线程只能对应一个对象,实际上,Java语言本身提供了ThreadLocal并发工具类,可以更加轻松的实现线程唯一单例。

  2. 集群环境下的单例

    集群唯一:指的是进程内、进程间也唯一。即不同的进程间共享同一个对象,不能创建同一个类的多个对象。

    我们需要把这个单例对象序列化并存储到外部共享存储区(如文件)。进程在使用这个单例对象的时候,需要先从外部共享存储区中将它读取到内存中,并反序列化成对象,然后再使用,使用完成之后还需要再存储回外部共享存储区。为了保证任何时刻在进程间都只有一份对象存在,一个进程在获取到对象之后,需要对对象加锁,避免其他进程再将其获取。再进程使用完这个对象之后,需要显示的将对象从内存中删除并且释放对对象的加锁。

九、如何实现一个多例模式

“单例”指的是一个类只能创建一个类,对应地,“多例”指的是一个类可以创建多个对象,但是个数是有限制的,比如只能创建3个对象。多例的实现也比较简单,通过一个Map来存储对象类型和对象之间的对应关系,来控制这个对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值