单例模式

一开始,我们写的单例模式是这样的。

1
2
3
4
5
6
7
8
9
public class Singleton {
private static Singleton instance = null;
public static Singleton getInstance(){
if(instance==null){
instance = new Singleton();
}
return instance;
}
}

但是,这种方式有一个问题,就是无法解决多线程问题。

于是,对方法加锁:

1
2
3
4
5
6
7
8
9
public class Singleton {
private static Singleton instance = null;
public static synchronized Singleton getInstance(){
if(instance==null){ //lineA
instance = new Singleton();
}
return instance;
}
}

这样,就解决了并发的问题。

但是,在每次调用方法我们都需要加锁,加锁实际上性能会变低,实际上调用方法只会出现一次instance==null,以后的每一次调用都是直接返回instance对象。

因此,可以将if判断语句提取出来。

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {
private static Singleton instance = null;
public static Singleton getInstance(){
if(instance==null) { //lineA
synchronized (this) {
instance = new Singleton();
}
}
return instance;
}
}

但是,这样还是有问题,假设线程A执行完了lineA进入同步语句,但还并没有创建实例,此时线程B也执行到了lineA,但是instance==null,还是会重复创建实例。

于是,采用DCL解决,在同步语句块内部再进行一次if判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {
private static Singleton instance = null;
public static Singleton getInstance(){
if(instance==null) {
synchronized (this) {
if(instance==null) {
instance = new Singleton();
}
}
}
return instance;
}
}

那么,这样就没有问题了吗?

并不是,因为new Singleton()这个语句实际上它并不是一个原子操作

它有三条指令构成:

  • 1.在堆中开辟一块内存(new)

  • 2.调用对象的构造函数对内存进行初始化(invokespecial)

  • 3.最后将引用赋值给变量(astore),这一句instance就赋值了

所以,因为重排序的存在,CPU有可能产生指令重排序,比如1-3-2,这样的话,另一个线程可能在对象还没有初始化的时候就拿走了instance,造成问题。

于是,我们可以加上volatile,禁止指令重排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {
private static volatile Singleton instance = null;
public static Singleton getInstance(){
if(instance==null) {
synchronized (this) {
if(instance==null) {
instance = new Singleton();
}
}
}
return instance;
}
}

单例模式