持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情
1 不要去 开课吧 问原因就 自己百度去
2 上节回顾/本节重点/上节思考题
2.1 本节讲解
1. 实现一个线程安全的单例
2 为什么要使用单例?
3 单例存在哪些问题?
4 单例与静态类的区别?
5 有何替代的解决方案?
6 单例这种设计模式存在哪些问题?
2.3 上节 思考题解答
4 如何实现一个单例?
4.1 饿汉式
一开始 的时候就加载/初始化 资源 (不需要延迟加载的话 推荐这种)
public class IdGenerator {
private AtomicLong id = new AtomicLong(0);
private static final IdGenerator instance = new IdGenerator();
private IdGenerator() {}
public static IdGenerator getInstance() {
return instance;
}
public long getId() {
return id.incrementAndGet();
}
}
4.2 懒汉式
支持延迟加载 如果频繁地用到,那频繁加锁、释放锁及并发度低等问题,会导致性能瓶颈,这种实现方式就不可取了
public class IdGenerator {
private AtomicLong id = new AtomicLong(0);
private static IdGenerator instance;
private IdGenerator() {}
public static synchronized IdGenerator getInstance() {
if (instance == null) {
instance = new IdGenerator();
}
return instance;
}
public long getId() {
return id.incrementAndGet();
}
}
4.3 双重检测
虽然都是类锁,但懒汉式每次方法调用都会尝试获取锁,双重检测则不会。
public class IdGenerator {
private AtomicLong id = new AtomicLong(0);
private static IdGenerator instance;
private IdGenerator() {}
public static IdGenerator getInstance() {
if (instance == null) {
synchronized(IdGenerator.class) { // 此处为类级别的锁
if (instance == null) {
instance = new IdGenerator();
}
}
}
return instance;
}
public long getId() {
return id.incrementAndGet();
}
}
4.4 静态内部类
public class IdGenerator {
private AtomicLong id = new AtomicLong(0);
private IdGenerator() {}
private static class SingletonHolder{
private static final IdGenerator instance = new IdGenerator();
}
public static IdGenerator getInstance() {
return SingletonHolder.instance;
}
public long getId() {
return id.incrementAndGet();
}
}
4.5 枚举
最简单的实现方式,基于枚举类型的单例实现。这种实现方式通过 Java 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。
Java规范字规定,每个枚举类型及其定义的枚举变量在JVM中都是唯一的,因此在枚举类型的序列化和反序列化上,Java做了特殊的规定。在序列化的时候Java仅仅是将枚举对象的name属性输到结果中,反序列化的时候则是通过java.lang.Enum的valueOf()方法来根据名字查找枚举对象。也就是说,序列化的时候只将DATASOURCE这个名称输出,反序列化的时候再通过这个名称,查找对应的枚举类型,因此反序列化后的实例也会和之前被序列化的对象实例相同。
public enum IdGenerator {
INSTANCE;
private AtomicLong id = new AtomicLong(0);
public long getId() {
return id.incrementAndGet();
}
}
5 单例模式的缺点
5.1 单例对 OOP 特性的支持不友好
单例的使用是直接获取了实际对象,故违背了面向接口编程的设计原则,无法保证开放-闭合原则。
5.2 单例会隐藏类之间的依赖关系
要表达的是某个类要是依赖了单例,不仔细阅读代码是不容易发现的,降低了代码的可读性,不能一眼就看出这个类对其他类的依赖关系。
5.3 单例对代码的扩展性不友好
5.4 单例对代码的可测试性不友好
5.5 单例不支持有参数的构造函数
配置文件与单例对象有良好的适应性,不再需要传入可变参数来来创建单例对象,而是采用静只加载一次加的配置文件决定,对使用者可以隐藏参数的传递,若要修改参数,就修改配置文件后重启
public class Config {
public static final int PARAM_A = 123;
public static final int PARAM_B = 245;
}
public class Singleton {
private static Singleton instance = null;
private final int paramA;
private final int paramB;
private Singleton() {
this.paramA = Config.PARAM_A;
this.paramB = Config.PARAM_B;
}
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
6 有上面这么多问题 那怎么办呢?
6.1 有何替代解决方案?
6.1.1 静态方法实现方式
// 静态方法实现方式
public class IdGenerator {
private static AtomicLong id = new AtomicLong(0);
public static long getId() {
return id.incrementAndGet();
}
}
// 使用举例
long id = IdGenerator.getId();
6.1.2 依赖注入
基于新的使用方式,我们将单例生成的对象,作为参数传递给函数(也可以通过构造函数传递给类的成员变量),可以解决单例隐藏类之间依赖关系的问题。不过,对于单例存在的其他问题,比如对 OOP 特性、扩展性、可测性不友好等问题,还是无法解决。
// 1. 老的使用方式
public demofunction() {
//...
long id = IdGenerator.getInstance().getId();
//...
}
// 2. 新的使用方式:依赖注入
public demofunction(IdGenerator idGenerator) {
long id = idGenerator.getId();
}
// 外部调用demofunction()的时候,传入idGenerator
IdGenerator idGenerator = IdGenerator.getInsance();
demofunction(idGenerator);
7 前面思考和应用场景解答 (在此之前 请再次思考前面的问题 )
7.1 实现一个线程安全的单例
一般使用 饿汉式 / 静态内部类
7.2 为什么要使用单例?
防止资源重复加载 造成浪费
7.3 单例存在哪些问题?
扩展性/多态/继承/抽象 等方面都收到影响,要仔细根据业务思考
7.4 单例与静态类的区别?
扩展性/多态/继承/抽象 等方面都收到影响,要仔细根据业务思考
7.5 有何替代的解决方案?
static / 依赖注入 / 工厂类等方式
7.6 springboot 开发的使用 单例模式能用在哪里
8 总结:
9 下节预知
- 工厂模式
11 思考题
单例的在实际场景中 怎么使用的,可以评论打出