单例模式

在一些程序设计中,希望对象只有一个实例,这时候就可以使用单例模式。

单例模式的实现,在语法上 用一个私有的构造方法来保护类不能在外部被 new 出来,然后提供一个静态方法返回唯一的实例即可。

应用场景,例如:系统配置,整个系统有一个配置对象即可,如果有配置修改,通知这个唯一的对象就好了,每次读取配置只需从这个唯一的对象中获取。

下面是一些常见的写法,以及优缺点:

代码一

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

private final static Singleton1 instance = new Singleton1();

static {
// 在这里初始化 instance 其实都一样,都是在类初始化即实例化instance。
}

private Singleton1() {
// 通过私有构造方法禁止外部通过new创建对象
}

public static Singleton1 getInstance() {
return instance;
}

}

这是最简单的单例模式,在多线程的情况下依然能保持单例。

这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,没有达到lazy loading的效果。

代码二

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

private static Singleton2 instance;

private Singleton2() {
}

public static Singleton2 getInstance() {
if (null == instance) {
// 在多线程的时候这里会出问题,导致的后果就是创建了多个实例
instance = new Singleton2();
}
return instance;
}
}

在调用getInstance方法时才实例化对象,但多线程环境下会创建出多个对象。

代码三

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

private static Singleton3 instance;

private Singleton3() {
}

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

这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,除了第一次,其它情况都不需要同步。

代码四

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

private volatile static Singleton4 instance;

private Singleton4() {
}

public static Singleton4 getInstance() {
if (instance == null) {
// 只有instance还没被实例化的时候才会到这里,但有可能多个线程都执行到这了。
synchronized (Singleton4.class) {
// 进入到if中的线程有多个,前面的线程可能已经实例化了instance,所以需要再次判断。
if (instance == null) {
instance = new Singleton4();
}
}
}
return instance;
}

}

双检锁,解决同步锁的性能问题,只有还没实例化的时候才会锁

代码五

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

private static class SingletonHolder {
private static final Singleton5 INSTANCE = new Singleton5();
}

private Singleton5() {
}

public static Singleton5 getInstance() {
return SingletonHolder.INSTANCE;
}

}

通过内部类实现的,其它还有通过枚举实现的。

测试

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
102
103
104
105
106
107
108
109
110
111
import org.junit.Assert;
import org.junit.Test;

import com.school1024.dp.DesignPattern;

public class SingletonTest {

@Test
public void test0() {
DesignPattern dp1 = new DesignPattern();
DesignPattern dp2 = new DesignPattern();

Assert.assertFalse(dp1.equals(dp2));
Assert.assertFalse(dp1.hashCode() == dp2.hashCode());

}

@Test
public void test1() {
Singleton1 ins1 = Singleton1.getInstance();
Singleton1 ins2 = Singleton1.getInstance();

Assert.assertTrue(ins1.equals(ins2));
Assert.assertTrue(ins1.hashCode() == ins2.hashCode());

System.out.println(ins1);
System.out.println(ins1.hashCode());

// 模拟多线程的场景
Runnable r = new Runnable() {
@Override
public void run() {
Singleton1 single = Singleton1.getInstance();
System.out.println(single.hashCode());
}
};

for (int i = 0; i < 10; i++) {
new Thread(r).start();
}
}

@Test
public void test2() {

// 模拟多线程的场景,多运行几次会发现有不一样的hashCode
Runnable r = new Runnable() {
@Override
public void run() {
Singleton2 single = Singleton2.getInstance();
System.out.println(single.hashCode());
}
};

for (int i = 0; i < 10; i++) {
new Thread(r).start();
}
}

@Test
public void test3() {

// 模拟多线程的场景,无论怎么运行hashCode都是一样的
Runnable r = new Runnable() {
@Override
public void run() {
Singleton3 single = Singleton3.getInstance();
System.out.println(single.hashCode());
}
};

for (int i = 0; i < 10; i++) {
new Thread(r).start();
}
}

@Test
public void test4() {

// 模拟多线程的场景,无论怎么运行hashCode都是一样的

Runnable r = new Runnable() {
@Override
public void run() {
Singleton4 single = Singleton4.getInstance();
System.out.println(single.hashCode());
}
};

for (int i = 0; i < 10; i++) {
new Thread(r).start();
}
}

@Test
public void test5() {

Runnable r = new Runnable() {
@Override
public void run() {
Singleton5 single = Singleton5.getInstance();
System.out.println(single.hashCode());
}
};

for (int i = 0; i < 10; i++) {
new Thread(r).start();
}
}

}
  • test0:测试出普通类new出来的2个对象 地址和hashCode都不一样
  • test1:模拟线程测试,hashCode是一样的
  • test2:模拟线程测试,会有hashCode不一样的情况,这是多线程造成的。
  • test3:模拟线程测试,无论怎么测试,hashCode都一样
  • test4:模拟线程测试,无论怎么测试,hashCode都一样
  • test5:模拟线程测试,无论怎么测试,hashCode都一样
分享到