Skip to content

Commit abe9250

Browse files
author
zhupeiquan
committed
What_is_an_efficient_way_to_implement_a_singleton_in_Java.md
1 parent 57d785f commit abe9250

File tree

1 file changed

+164
-0
lines changed

1 file changed

+164
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# 如何创建单例 ?
2+
3+
## 问题
4+
Java 创建单例有哪些方式 ?
5+
6+
## 解答
7+
实现单例,从加载方式来看,有两种:
8+
9+
- 预加载
10+
- 懒加载
11+
12+
先看一下实现单例最简单的方式(预加载):
13+
```
14+
public class Foo {
15+
16+
private static final Foo INSTANCE = new Foo();
17+
18+
private Foo() {
19+
if (INSTANCE != null) {
20+
throw new IllegalStateException("Already instantiated");
21+
}
22+
}
23+
24+
public static Foo getInstance() {
25+
return INSTANCE;
26+
}
27+
}
28+
```
29+
30+
再来看一下懒加载的方式:
31+
```
32+
class Foo {
33+
34+
private static Foo INSTANCE = null;
35+
36+
private Foo() {
37+
if (INSTANCE != null) {
38+
throw new IllegalStateException("Already instantiated");
39+
}
40+
}
41+
42+
public static Foo getInstance() {
43+
if (INSTANCE == null) {
44+
INSTANCE = new Foo();
45+
}
46+
return INSTANCE;
47+
}
48+
}
49+
```
50+
51+
以上方式在单线程的情况可以很好的满足需要,换言之,若是在多线程,还需要作一定的改进,如下所示:
52+
```
53+
class Foo {
54+
// 请注意 volatile 关键字的使用
55+
private static volatile Foo INSTANCE = null;
56+
57+
private Foo() {
58+
if (INSTANCE != null) {
59+
throw new IllegalStateException("Already instantiated");
60+
}
61+
}
62+
63+
public static Foo getInstance() {
64+
if (INSTANCE == null) { // Check 1
65+
synchronized (Foo.class) {
66+
if (INSTANCE == null) { // Check 2
67+
INSTANCE = new Foo();
68+
}
69+
}
70+
}
71+
return INSTANCE;
72+
}
73+
}
74+
```
75+
76+
上述代码运用了 [Double-Checked Locking idiom](http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html)
77+
78+
解决了多线程环境下的单例,可以进一步思考如何实现可序列化的单例 ? 反序列化可以不通过构造函数直接生成一个对象,所以反序列化时,我们需要保证其不再创建新的对象。
79+
80+
```
81+
class Foo implements Serializable {
82+
83+
private static final long serialVersionUID = 1L;
84+
85+
private static volatile Foo INSTANCE = null;
86+
87+
private Foo() {
88+
if (INSTANCE != null) {
89+
throw new IllegalStateException("Already instantiated");
90+
}
91+
}
92+
93+
public static Foo getInstance() {
94+
if (INSTANCE == null) { // Check 1
95+
synchronized (Foo.class) {
96+
if (INSTANCE == null) { // Check 2
97+
INSTANCE = new Foo();
98+
}
99+
}
100+
}
101+
return INSTANCE;
102+
}
103+
104+
@SuppressWarnings("unused")
105+
private Foo readResolve() {
106+
return INSTANCE;
107+
}
108+
}
109+
```
110+
111+
readResolve 方法可以保证,即使程序在上一次运行时序列化过此单例,也只会返回全局唯一的单例。对于 Java 对象序列化机制,可参考[附录拓展](#appendix)
112+
113+
java 创建单例的方法基本实现了,不过我们还可以作进一步的改进 —— 代码重构:
114+
```
115+
public final class Foo implements Serializable {
116+
117+
private static final long serialVersionUID = 1L;
118+
119+
// 使用内部静态 class 实现懒加载
120+
private static class FooLoader {
121+
// 保证在多线程环境下无差错运行
122+
private static final Foo INSTANCE = new Foo();
123+
}
124+
125+
private Foo() {
126+
if (INSTANCE != null) {
127+
throw new IllegalStateException("Already instantiated");
128+
}
129+
}
130+
131+
public static Foo getInstance() {
132+
return FooLoader.INSTANCE;
133+
}
134+
135+
@SuppressWarnings("unused")
136+
private Foo readResolve() {
137+
return FooLoader.INSTANCE;
138+
}
139+
}
140+
141+
好了,现在已经很完美实现了单例的创建,是不是很高兴。单例实线的基本原理,我们已经基本清楚里,最后提供一种更加简洁方法,如下:
142+
```
143+
public enum Foo {
144+
INSTANCE;
145+
}
146+
```
147+
148+
为什么可以这么简洁?因为 Java 中每一个枚举类型都默认继承了 java.lang.Enum ,而 Enum 实现了 Serializable 接口,所以枚举类型对象都是默认可以被序列化的。通过反编译,也可以知道枚举常量本质上就是一个
149+
```
150+
public static final xxx
151+
````
152+
153+
154+
155+
对于枚举的进一步理解,请参考[附录拓展](#appendix)。
156+
157+
<a id="appendix" name="appendix" />
158+
附录拓展:
159+
[深入理解 Java 对象序列化](http://developer.51cto.com/art/201202/317181.htm)
160+
[对象的序列化和反序列化](http://www.blogjava.net/lingy/archive/2008/10/10/233630.html)
161+
[通过反编译字节码来理解 Java 枚举](http://unmi.cc/understand-java-enum-with-bytecode/)
162+
163+
stackoverflow原址:http://stackoverflow.com/questions/70689/what-is-an-efficient-way-to-implement-a-singleton-pattern-in-java
164+

0 commit comments

Comments
 (0)