多线程中单例模式的优化

单例模式

在编程中,单例模式是我们常用的一种设计模式,功能是保证在整个系统只用一个该对象的对象,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

public class Singleton {
private static Singleton singleton;

private Singleton() {
}

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

上面的代码我们知道并不是线程安全的,在多线程环境下,容易造成创建多个对象。 测试代码如下:

1
2
3
4
5
6
7
8
9
10
11

@Test
public void testSingleton() throws InterruptedException {
for (int i=0;i<10;i++){
new Thread(()->{
Singleton.getInstance();
}).start();
}
Thread.currentThread().join();
}

运行结果如下:

1
2
3
4
5
6
7
创建对象
创建对象
创建对象
创建对象
创建对象
创建对象
创建对象

解决方案


对于上面的问题解决的方法有很多,比如使用加锁的方式,double检测的方式,为了验证最有方案我们把代码修改下:

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 static Singleton singleton;

private Singleton() {
try {
Thread.sleep(10);//增加创建对象的耗时
} catch (Exception e) {

}
}

public static Singleton getInstance() {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
return singleton;
}
}
return singleton;
}
}

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

@Test
public void testSingleton() throws InterruptedException {
long start=System.currentTimeMillis();
List<Thread> threadList = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
Thread thread = new Thread(() -> {
Singleton.getInstance();
});
threadList.add(thread);
thread.start();
}
for (Thread t : threadList) {
t.join();
}
long end=System.currentTimeMillis();
System.out.println("运行耗时:"+(end-start));
}


方案一:使用synchronized 关键字

1
2
3
4
5
6
7
8
9
10
public static Singleton getInstance() {
synchronized (Singleton.class){
if (singleton == null) {
System.out.println("创建对象");
singleton = new Singleton();
}
}
return singleton;
}

经过多次测试时间维持在410ms左右,下面是一次测试结果

1
2
3

运行耗时:410


这个虽然成功的保证了只有一个对象,但同样也会把其他的线程阻塞在创建的锁的前面,造成了性能上面的开销,如果创建一个对象的时间比较长,这个性能的开销是相当可观的。

方案二:double验证

1
2
3
4
5
6
7
8
9
10
11
12
13

public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
return singleton;
}
}
}
return singleton;
}

1
2
3

运行耗时:380

上面的代码虽然聪明的避开的过多线程等待的原因,但是彻底消除线程排队的现象,因为创建对象分需要耗时,这样就给其他线程提供了“可乘之机”

方案三:使用volatile共享变量 (最优方案)

共享变量是线程间同步的“轻量级锁”,彻底消除线程排队的现象,此处用于单例模式的设计,能够实现最小性能的开销:

1
2
private volatile static Singleton singleton;

1
2
3

运行耗时:280