引言
通过单例模式,我们能够保证获取到的对象始终都是同一个对象。单例模式又有两种写法:饿汉式和懒汉式。
饿汉式
我们先来看看饿汉式的单例模式是怎么编写的.
public class Singleton {
private static Singleton instance = new Singleton(); //预先初始化
private Singleton() {} //将构造函数私有
public static Singleton getInstance() { //对外提供获取单例的方法
return instance;
}
}
线程不安全的懒汉式
看完饿汉式的单例模式,接下来我们来看看懒汉式的写法:
public class Singleton {
private static Singleton instance;
private Singleton() {} //将构造函数私有
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
如果不加思考,我相信很多人都会将懒汉式的单例模式写成上面这个样子。认真思索一下后,我们很容易发现这样是线程不安全的。当有多个线程同时执行getInstance方法时,就会出现多次实例化对象的情况。
同步的懒汉式(效率低)
很自然的,我们就想到使用synchronized关键字来实现同步。因此,我们的代码就变成下面这个样子:
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
如果这样写的话,每次访问这个方法(无论对象是否已经创建),我们都要进行同步。这样效率太过于低下了。
双重校验锁(存在隐患)
为了提高效率,我们可以使用双重校验锁来避免每个访问都需要同步的问题。
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
看上去,上面的代码好像已经没什么问题,但实际上还存在一个很大的隐患。
instance = new Singleton(); 这行实例化对象的代码,其实可以分为三个步骤:
- 分配内存空间
- 初始化对象
- 将对象指向刚刚分配的内存
但实际上,有些编译器为了提高效率,可能会将代码重排序,变成下面的顺序
- 分配内存空间
- 将对象指向刚刚分配的内存
- 初始化对象
接下来分析一下代码重排序后的运行状况:
| Time | Thread1 | Thread2 |
|---|---|---|
| T1 | 检查到instance为空 | |
| T2 | 获取锁 | |
| T3 | 为instance分配内存空间 | |
| T4 | 将instance对象指向刚刚分配的内存空间 | |
| T5 | 检查到instance不为空 | |
| T6 | 访问instance实例(此时instance尚未初始化) | |
| T7 | 初始化instance对象 |
我们可以看到,在多线程的情况下,可能会出现一个线程获取到的是未初始化的对象。
双重校验锁(最终版)
为了保证线程能够获取到最终初始化的对象,我们需要使用volatile关键字,禁止代码的重排序,从而解决上面的问题。
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}