高并发下线程安全的单例模式

今天发现对以往学习的单例模式掌握不够深入,特别是多线程下的线程安全问题,于是学习了高并发下如何保证单例模式的线程安全性。

单例模式的概念:单例模式是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种模式方法。

从概念中体现出了单例的一些特点:

  1. 在任何情况下,单例类永远只有一个实例存在

  2. 单例需要有能力为整个系统提供这一唯一实例

两种最基本的单例实现

一、饿汉式(线程安全)

饿汉式单例是指在方法调用前,实例就已经创建好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MySingleton {
//私有构造方法,不允许外部直接创建对象
private MySingleton(){}
//创建类的唯一实例
private static MySingleton instance = new MySingleton();
//提供一个获取该实例的方法
public static MySingleton getInstance() {
return instance;
}
}

饿汉式多线程并发测试

多线程执行方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MyThread extends Thread{
@Override
public void run() {
System.out.println(MySingleton.getInstance().hashCode());
}
public static void main(String[] args) {
//开启十条线程
MyThread[] mts = new MyThread[10];
for(int i = 0 ; i < mts.length ; i++){
mts[i] = new MyThread();
}
for (int j = 0; j < mts.length; j++) {
mts[j].start();
}
}
}

测试结果和结论

从运行结果可以看出实例变量额hashCode值一致,这说明对象是同一个,饿汉式单例是线程安全的。

1
2
3
4
5
6
7
8
9
10
1718900954
1718900954
1718900954
1718900954
1718900954
1718900954
1718900954
1718900954
1718900954
1718900954

二、懒汉式(线程不安全)

懒汉式单例是指在方法调用获取实例时才创建实例,因为相对饿汉式显得“不急迫”,所以被叫做“懒汉模式”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MySingleton {
//私有构造方法,不允许外部直接创建对象
private MySingleton(){}
//声明类的唯一实例,使用private static修饰
private static MySingleton instance = null;
//提供一个获取该实例的方法
public static MySingleton getInstance() {
if(instance == null){
instance = new MySingleton();
}
return instance;
}
}

懒汉式多线程并发测试

首先稍微修改代码,让线程当前暂时休眠,然后执行上面饿汉式的多线程调用方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MySingleton {
private MySingleton(){}
private static MySingleton instance = null;
public static MySingleton getInstance() {
try {
if(instance != null){
}else{
//创建实例之前可能会有一些准备性的耗时工作
Thread.sleep(300);
instance = new MySingleton();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return instance;
}
}

测试结果和结论

从这里执行结果可以看出,单例的线程安全性并没有得到保证.

1
2
3
4
5
6
7
8
9
10
1210420568
1210420568
1935123450
1718900954
1481297610
1863264879
369539795
1210420568
1210420568
602269801

线程安全的懒汉式单例

要保证线程安全,我们就得需要使用同步锁机制,下面就来看看我们如何一步步的解决 存在线程安全问题的懒汉式单例。

一、使用synchronized关键字

出现非线程安全问题,是由于多个线程可以同时进入getInstance()方法,那么只需要对该方法进行synchronized的锁同步即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MySingleton {
private MySingleton(){}
private static MySingleton instance = null;
public synchronized static MySingleton getInstance() {
try {
if(instance != null){
}else{
//创建实例之前可能会有一些准备性的耗时工作
Thread.sleep(300);
instance = new MySingleton();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return instance;
}
}

然后执行MyThread测试方法。

测试结果和结论

从执行结果上来看,问题已经解决了,但是这种实现方式的运行效率会很低。同步方法效率低,那我们考虑使用同步代码块来实现。

1
2
3
4
5
6
7
8
9
10
1689058373
1689058373
1689058373
1689058373
1689058373
1689058373
1689058373
1689058373
1689058373
1689058373

二、使用同步代码块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MySingleton {
private MySingleton(){}
private static MySingleton instance = null;
//public synchronized static MySingleton getInstance() {
public static MySingleton getInstance() {
try {
synchronized (MySingleton.class) {
if(instance != null){
}else{
//创建实例之前可能会有一些准备性的耗时工作
Thread.sleep(300);
instance = new MySingleton();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return instance;
}
}

这里的实现能够保证多线程并发下的线程安全性,但是这样的实现将全部的代码都被锁上了,同样的效率很低下。

三、使用Double Check Locking 双检查锁机制(推荐)

为了达到线程安全,又能提高代码执行效率,我们这里可以采用DCL的双检查锁机制来完成,代码实现如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class MySingleton {
//使用volatile关键字保共享变量的可见性
volatile private static MySingleton instance = null;
private MySingleton(){}
public static MySingleton getInstance() {
try {
if(instance != null){
}else{
//创建实例之前可能会有一些准备性的耗时工作
Thread.sleep(300);
synchronized (MySingleton.class) {
//双检查锁
if(instance == null){
instance = new MySingleton();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return instance;
}
}

测试结果及结论

使用了volatile关键字来保证其线程间的可见性;在同步代码块中使用二次检查,以保证其不被重复实例化。集合其二者,这种实现方式既保证了其高效性,也保证了其线程安全性。

1
2
3
4
5
6
7
8
9
10
369539795
369539795
369539795
369539795
369539795
369539795
369539795
369539795
369539795
369539795