带着下面问题学习
- 为什么要使用单例?
- 单例存在哪些问题?
- 单例与静态类的区别?
-
为什么要使用单例?
定义
单例设计模式:一个类只允许创建一个对象实例,那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。
如何实现一个单例
关注点:
构造函数需要是 private 访问权限,这样才能避免外部通过 new 创建实例;
- 考虑对象创建时的线程安全问题;
- 考虑是否支持延迟加载;
- 考虑 getInstance() 性能是否高(是否加锁);
简单介绍几种经典的实现方式:
1、饿汉式
在类加载的时候,instance 静态实例就会创建并初始化好。属于线程安全。但不支持延迟加载。
public class IdGenerator {private AtomicLong id = new AtomicLong(0);private static final IdGenerator instance = new IdGenerator();private IdGenerator() {}private static IdGenerator getInstance() {return instance;}public long getId() {return id.incrementAndGet();}}
2、懒汉式
懒汉式相对于饿汉式的优势是支持延迟加载。缺点是 getInstance() 加了 synchronized,导致高并发的时候,性能低下。
public class IdGenerator {private AtomicLong id = new AtomicLong(0);private static final IdGenerator instance;private IdGenerator() {}private static synchronized IdGenerator getInstance() {if (instance == null) {instance = new IdGenerator();}return instance;}public long getId() {return id.incrementAndGet();}}
3、双重检测
解决了饿汉式和懒汉式的问题,既有延迟加载,又支持高并发。
public class IdGenerator {private AtomicLong id = new AtomicLong(0);private static final IdGenerator instance;private IdGenerator() {}private static IdGenerator getInstance() {if (instance == null) {synchronized(IdGenerator.class) { // 类级别锁if (instance == null) {instance = new IdGenerator();}}}return instance;}public long getId() {return id.incrementAndGet();}}
4、静态内部类
比双重检测更简单的实现方式,利用了 Java 的静态内部类。在调用 getInstance() 的时候,SingletonHolder 才会被加载。
public class IdGenerator {private AtomicLong id = new AtomicLong(0);private static final IdGenerator instance;private IdGenerator() {}private static class SingletonHolder {private static final IdGenerator instance = new IdGenerator();}private static IdGenerator getInstance() {return SingletonHolder.instance;}public long getId() {return id.incrementAndGet();}}
5、枚举
通过 Java 枚举类本身的特性,保证了实例创建的线程安全性和实例的唯一性。
public enum IdGenerator {INSTANCE;private AtomicLong id = new AtomicLong(0);private long getId() {return id.incrementAndGet();}}
单例存在哪些问题?
- 单例对OOP的四大特性(封装、抽象、继承、多态)支持不友好
IdGenerator 的使用方式违背了基于接口而非实现的设计原则,也违背了广义上理解的 OOP 的抽象特性。如果后续需要针对不同的业务,采取不同的 ID 生成算法,那么改动就比较大。
除此之外,单例对继承和多态特性的支持也不友好。
- 单例会隐藏类之间的依赖关系
单例类的创建不需要显式创建、不需要依赖参数传递。这样在代码阅读的时候,需要仔细查看每个函数的代码实现,才能知道这个类到底依赖了哪些单例类。
- 单例对代码的扩展性不友好
如果我们把单例类,需要在代码中创建两个实例或者多个实例,代码会改动比较大。
- 单例对代码的可测试性不友好
单例模式的这种硬编码式的使用方式,在写单元测试的时候,无法使用 mock 的方式替换掉单例模式里的外部资源(如 DB)。
- 单例不支持有参数的构造函数
因为单例模式只会创建一个实例,所以就算有有参数的构造函数,也只会在首次创建实例的时候起作用。
单例与静态类的区别?
静态方法的这种实现思路,并不能解决我们之前提到的问题。实际上,它比单例更加的不灵活,比如,它无法支持延迟加载。
// 静态方法实现方式public class IdGenerator {private static AtomicLong id = new AtomicLong(0);private static long getId() {return id.incrementAndGet();}}// 使用举例long id = IdGenerator.getId();
有何替代解决方案
可以通过将单例生成的对象,作为参数传递给函数,可以解决单例隐藏类之间的依赖关系的问题,其他问题还是无法解决。
// 1. 老的使用方式public demofunction() {long id = IdGenerator.getInstance().getId();}// 2. 新的使用方式:依赖注入public demofunction(IdGenerator idGenerator) {long id = idGenerator.getId();}// 外部调用 demofunction 的时候,传入 idGeneratorIdGenerator idGenerator = IdGenerator.getInstance();demofunction(idGenerator);
