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