Fork me on GitHub

单例模式的几种写法对比

懒汉模式

1. 最简单的懒汉模式

线程不安全,不推荐

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {

private Singleton() {}

private static Singleton instance = null;

public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

在多线程环境下,当多个线程执行到第9行代码时,此时instance都为空,那么这些线程都会执行第10行代码,从而创建了多个对象

2. 在上面的基础上给getInstance方法加上synchronized

线程安全,不推荐,加上synchronized之后,方法内的所有实现在同一时间只允许一个线程访问,可以保证线程安全,但是synchronized会带来性上很大的开销

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {

private Singleton() {}

private static Singleton instance = null;

public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

3. 双重同步锁单例模式

线程不安全,可以改进

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Singleton {

private Singleton() {}

private static Singleton instance = null;

public static Singleton getInstance() {
if (instance == null) { // 双重检测机制
synchronized (Singleton.class) { // 同步锁
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

对于instance = new Singleton(); 这一行代码,实际上可以看出以下三步:
1、memory = allocate() 分配对象的内存空间
2、ctorInstance() 初始化对象
3、instance = memory 设置instance指向刚分配的内存
由于第二步和第三步不能由先行发生原则到处出来,JVM和cpu优化,发生了指令重排,顺序如下:
1、memory = allocate() 分配对象的内存空间
3、instance = memory 设置instance指向刚分配的内存
2、ctorInstance() 初始化对象
那么可能存在这样一种情况,在多线程情况下,当线程A执行到第10行代码instance = new Singleton();的第3步instance = memory 设置instance指向刚分配的内存时,线程B执行到第8行代码if (instance == null), 这时对象还没有初始化完毕,然而instance已经指向了分配给对象的内存,instance已经不为空,会造成对象逸出,因而线程不安全;

4. volatile + 双重检测机制,禁止指令重排

线程安全,推荐

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Singleton {

private Singleton() {}

private static volatile Singleton instance = null;

public static Singleton getInstance() {
if (instance == null) { // 双重检测机制
synchronized (Singleton.class) { // 同步锁
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

饿汉模式,单例实例在类装载时进行创建

如果构造方法中没有很多的处理,那么饿汉模式是可以接收的,但是如果构造方法中有非常多的处理,会导致类加载的时候很慢,会导致一些性能的问题。 如果只进行类的加载,而没有进行实际的调用,就会造成资源的浪费,因此使用饿汉模式的时候应该考虑两个问题:

  1. 构造方法有没有过多的处理
  2. 这个类是否一定会被使用,避免装载之后没有调用造成资源浪费

1. 普通饿汉模式

线程安全,可以改进

1
2
3
4
5
6
7
8
9
10
public class Singleton {

private Singleton() {}

private static Singleton instance = new Singleton();

public static Singleton getInstance() {
return instance;
}
}

2. 将创建对象的代码放到static块中

线程安全,推荐

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton {

private Singleton() {}

private static Singleton instance = null;

static {
instance = new Singleton();
}

public static Singleton getInstance() {
return instance;
}
}

枚举模式

最安全,推荐

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Singleton {

private Singleton() {}

public static Singleton getInstance() {
return Singleton.INSTANCE.getInstance();
}

private enum Singleton {
INSTANCE;

private Singleton singleton;

// JVM保证这个方法绝对只调用一次
Singleton() {
singleton = new Singleton();
}

public Singleton getInstance() {
return singleton;
}
}
}

-------------本文结束感谢您的阅读-------------