写点什么

单例设计模式

用户头像
桃子
关注
发布于: 2021 年 02 月 26 日
单例设计模式

一个类只允许创建一个对象。

单例设计模式的应用场景

  • 处理资源访问冲突。

例:多个线程中同时写入一个文件,如果每个线程都创建一个 FileWriter,会导致多个线程写入的内容相互覆盖。此时就可以使用单例设计模式,全局只创建一个 FileWriter,多个线程共享一个 FileWriter。

  • 全局唯一类。

某些数据在系统中只应保留一份,也比较适合用单例。如 ID 生成器。


单例实现的关键点

  • 构造函数需要是 private 访问权限的,这样才能避免外部通过 new 创建实例;

  • 考虑对象创建时的线程安全问题;

  • 考虑是否支持延迟加载;

  • 考虑 getInstance() 性能是否高(是否加锁)。


单例实现方式

饿汉式

关于懒汉式的争论:

反对者:

  • 饿汉式不支持延迟加载,如果实例占用资源多或初始化耗时长,比较浪费资源。最好的方法应该在用到的时候再去初始化。


支持者:

  • 如果初始化耗时长,最好不要等到真正要用它的时候,才去执行这初始化过程,这会影响到系统的性能(比如,在响应客户端接口请求的时候,做这个初始化操作,会导致此请求的响应时间变长,甚至超时)。采用饿汉式实现方式,将耗时的初始化操作,提前到程序启动的时候完成,这样就能避免在程序运行的时候,再去初始化导致的性能问题。

  • 如果实例占用资源多,按照 fail-fast 的设计原则(有问题及早暴露),那我们也希望在程序启动时就将这个实例初始化好。


示例代码:

public class SingletonPattern{	private static final SingletonPattern INSTANCE = new SingletonPattern();    private SingletonPattern(){}    public static SingletonPattern getInstance() {    	return INSTANCE;    }}
复制代码

缺点:不支持延迟加载;

优点:简单,安全,高效。


懒汉式

懒汉式相对于饿汉式的优势是支持延迟加载。其实现复杂度要比饿汉式高。


synchronized

示例代码:

public class SingletonPattern{	private static SingletonPattern instance ;  private SingletonPattern(){}  public static synchronized SingletonPattern(){  	if (instance == null) {        instance = new IdGenerator();     }     return instance;  }}
复制代码

缺点:效率低。


双重检测

示例代码:

public class SingletonPattern {  private final volatile SingletonPattern instance;  private SingletonPattern(){}  public static SingletonPattern getInstance(){  	if(instance == null) {    	synchronized(SingletonPattern.class) {      	if(instance == null) {        	instance = new SingletonPattern();        }      }    }    return instance;  }}
复制代码

优点:支持懒加载,支持高并发。


静态内部类

示例代码:

public class SingletonPattern {  private static SingletonPattern instance;	private SingletonPattern(){}  public static SingletonPattern getInstance(){  	return instance;  }    private static class SingletonHolder{  	private static final SingletonPattern instance = new SingletonPattern();  }}
复制代码

优点:线程安全,高效,支持懒加载。


枚举

通过枚举类型本身得特性,保证了示例创建的线程安全和示例的唯一性。

示例代码:

public enum SingletonEnum{	INSTANCE;  private static SingletonPattern instance;  Singleton(){     instance = new SingletonPattern();    }  public static SingletonPattern getInstance(){            return instance;  }}
复制代码


容器

如 Spring 容器。


单例存在的问题

单例对 OOP 特性支持不友好

  • 违背了基于接口而非实现的设计原则。即违背了 OOP 抽象的特性。

  • 单例对继承、多态特性的支持也不友好。单例继承会导致代码的可读性变差。


一旦你选择将某个类设计成到单例类,也就意味着放弃了抽象、继承和多态这三个强有力的面向对象特性,也就相当于损失了可以应对未来需求变化的扩展性。


单例会隐藏类之间的依赖关系

单例类不需要显示创建、不需要依赖参数传递,在函数中直接调用就可以了。如果代码比较复杂,这种调用关系就会非常隐蔽。在阅读代码的时候,我们就需要仔细查看每个函数的代码实现,才能知道这个类到底依赖了哪些单例类。


单例对代码扩展性不友好

单例类只能有一个对象实例。如果未来某一天,我们需要在代码中创建两个实例或多个实例,那就要对代码有比较大的改动。


单例对代码的可测性不好

单例模式的使用会影响到代码的可测试性。如果单例类依赖比较重的外部资源,比如 DB,我们在写单元测试的时候,希望能通过 mock 的方式将它替换掉。而单例类这种硬编码式的使用方式,导致无法实现 mock 替换。


单例不支持有参数的构造函数


单例的唯一性

即对象唯一性的作用范围。

通常我们所说的单例模式对象唯一指的时进程内唯一。相对应的还有我们的线程内唯一以及集群内唯一。

线程唯一实现方式:ThreadLocal

集群唯一:需要把这个单例对象序列化并存储到外部共享存储区,进程在使用这个单例对象的时候,需要先从外部共享存储区中将它读取到内存,并反序列化成对象,然后再使用,使用完成之后还需要再存储回外部共享存储区。


声明:本文所有内容整理自 极客时间 王争 老师的(设计模式之美 41~42),如果大家对原文感兴趣的话可前往购买学习,写得非常好。我不是销售人员,我只是一个快乐的搬运工。


用户头像

桃子

关注

还未添加个人签名 2018.04.24 加入

还未添加个人简介

评论

发布
暂无评论
单例设计模式