多线程中单例模式的优化

单例模式

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

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101

public class Singleton {
private static Singleton singleton;

private Singleton() {
}

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

```

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

``` Java

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

```

运行结果如下:

``` cte
创建对象
创建对象
创建对象
创建对象
创建对象
创建对象
创建对象
```


### 解决方案

---------------------------

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

``` Java

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;
}
}

```
**测试代码**:

``` Java

@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验证


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

运行耗时:380

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

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

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

private volatile static Singleton singleton;

运行耗时:280
作者

付威

发布于

2018-12-02

更新于

2018-12-02

许可协议

评论