终于来填坑了!这次是设计模式中的单例模式。
这段时间又看了一些关于并发、JVM 和 JMM 相关的内容,发现里面的部分内容例如并发中的 volatile 关键字、JVM 中的指令重排序、JMM 内存结构其实是跟单例模式紧密相关的,看完了那些对单例模式的理解就更深入了一些。
单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例
(摘自百度百科)
具体单例模式是什么我这里就不详细说了,我们还是具体来结合代码来理解下单例模式。
单例模式分为饿汉式(非延迟加载)和懒汉式(延迟加载)
1、饿汉式
比较简单,非延时加载,一上来就把对象给 new 出来了,这是天然的线程安全的。
public class Singleton {
private Singleton() {}
private static final Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
2、懒汉式
- 普通版本
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这个版本虽然线程上是安全的,但是存在严重的效率问题,每次判断时都要进行加锁。
- 双重检测版本
public class Singleton {
private volatile static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 1
synchronized (Singleton.class) {// 2
if (instance == null) {// 3
instance = new Singleton();// 4
}
}
}
return instance;
}
}
注意 instance 中的volatile
关键字,先说如果不存在这个关键字的话,该版本会出现什么问题。
我们知道 JVM 在执行指令时,在不影响最终结果及不违反 happen-before 的规则的时候,会对指令进行优化重排序,这在单线程的环境下是没有问题的,但是一旦在多线程的环境下就相当容易出现问题。
例如如果不存在volatile
关键字,那么在4
处代码的第三处初始化的过程可能会被优化成 1.分配内存空间-》2.地址引用赋值给instance-》3.初始化Singleton对象
。
单线程下是没有问题的,但是如果在多线程的情况下,线程 A 刚好到 2 的时候,线程 B 刚好到 1 进行判断,instance 确实不为空然后返回,这时候其实 instance 指向的地址中 Singleton 对象并没有初始化完成,这时候就会出现问题。
然而有了volatile
关键字后,可以禁止 JVM 执行 instance 相关指令时对其进行重排序,而其原理是通过内存屏障实现的(内存屏障目前为止还只是简单的看了一下,还没理解)。这样在执行的时候是按照正常的顺序1.分配内存空间-》2.初始化Singleton对象-》3.地址引用赋值给instance
执行。就不会出现上述问题。
至于应用场景,个人觉得在系统中需要用到唯一的实例来实现时就用单例模式就可以了,还是比较好判断的。