软件设计模式之单例模式
单例模式是软件设计中非常常见的模式,但真正用好也用对的好像还有很多路要走。一起来研究一下。
单例,最最起码得有这些吧
- 私有的构造方案
- 一个 static 的实例
- 一个 static 的外部访问入口
具体代码应该是这样的(懒汉模式)
1public class Singleton {
2
3 private static Singleton instance = null;
4
5 private Singleton(){}
6
7 public static Singleton getInstance() {
8 if (null == instance) {
9 instance = new Singleton();
10 }
11 return instance;
12 }
13}
当然了,看着好像是那么回事,完成了以上操作,我们来测试一下
1public class SingletonTest {
2
3 public static void main(String[] args) {
4 for (int i = 0; i < 3; i++) {
5 //test1
6 Singleton singleton = Singleton.getInstance();
7 System.out.println("running at thread" + Thread.currentThread().getName() + ">>" + singleton);
8 }
9 }
10}
输出(每次运行输出是不一样的)
1running at threadmain>>[email protected]
2running at threadmain>>[email protected]
3running at threadmain>>[email protected]
单例完成了!?
我们在多线程下测试一下,改造一下
1public class SingletonTest {
2
3 public static void main(String[] args) {
4// for (int i = 0; i < 3; i++) {
5// //test1
6// Singleton singleton = Singleton.getInstance();
7// System.out.println("running at thread" + Thread.currentThread().getName() + ">>" + singleton);
8// }
9
10
11 //test2
12 Runnable r = new Runnable() {
13 @Override
14 public void run() {
15 Singleton singleton = Singleton.getInstance();
16 System.out.println("running at thread" + Thread.currentThread().getName() + ">>" + singleton);
17 }
18 };
19 new Thread(r).start();
20 new Thread(r).start();
21 new Thread(r).start();
22 new Thread(r).start();
23 new Thread(r).start();
24 }
25}
输出(每次运行输出是不一样的)
1running at threadThread-2>>[email protected]
2running at threadThread-5>>[email protected]
3running at threadThread-4>>[email protected]
4running at threadThread-3>>[email protected]
5running at threadThread-0>>[email protected]
6running at threadThread-1>>[email protected]
出现问题了!!!怎么在多线程下出现了不同实例了???是不是我们的设计有问题呢?我们在图示位置打个断点,按照 Thread 来处理
然后 Debug 一下
我们先来看看 Thread0 的情况
看到此时instance
为{[email protected]}。我们换到 Thread1 再看看
看到此时
instance
也是{[email protected]}。往下走
额等等,怎么instance
变成{[email protected]}
了,被覆盖咯???
看来我们的设计有问题哦!
怎么改造呢?
从 Debug 来看,是getInstance()
时出现了线程安全问题,我们用synchronized
是不是可以解决问题呢?我们再改造一下
1public class Singleton {
2
3 private static Singleton instance = null;
4
5 private Singleton(){}
6
7 public synchronized static Singleton getInstance() {
8 if (null == instance) {
9 instance = new Singleton();
10 }
11 return instance;
12 }
13}
再 Debug 一下。
看到就一个 Thread 是 running 的,其他的是 Monitor 的,是不是可以了。我们往下继续走。
看到,Thread0 走出来之后,Thread4 才进入到 Running 状态。也就是加入
synchronized
把整个class都锁住了。目的达到,但是性能不好。我们再改造一下
1public class Singleton {
2
3 private static Singleton instance = null;
4
5 private Singleton(){}
6
7 public static Singleton getInstance() {
8 if (null == instance) {
9 synchronized(Singleton.class) {
10 instance = new Singleton();
11 }
12 }
13 return instance;
14 }
15}
再Debug 看看
现在所有 Thread 都是 Running 的了。我们继续走,先看看 Thread0
再看看 Thread3
看样子又要被覆盖咯!!!看来还是不行哦!是不是我们再加一把锁哦?!
我们再改造一下
1public class Singleton {
2
3 private static Singleton instance = null;
4
5 private Singleton(){}
6
7 public static Singleton getInstance() {
8 if (null == instance) {
9 synchronized(Singleton.class) {
10 if (null == instance) {
11 instance = new Singleton();
12 }
13 }
14 }
15 return instance;
16 }
17}
这次我们先 Run 一下看看结果,输出
1running at threadThread-2>>[email protected]
2running at threadThread-0>>[email protected]
3running at threadThread-1>>[email protected]
4running at threadThread-3>>[email protected]
5running at threadThread-4>>[email protected]
结果对咯!为什么呢?
其实,刚才的效果和不断的改造,我们把最初的Singleton
改造成了DoubleCheck 的懒汉式Singleton了。
当然了,上面这种方式只是一种方式罢了。下面直接上其他的方式Code。
2020.2.20更新
在Doublecheck的第一次check的时候,代码读取到instance不为null时,instance引用的对象有可能还没有完成初始化。主要原因是重排序,**所以只需要做一点小的修改(把instance声明为volatile型),就可以实现线程安全的延迟初始化。**因为被volatile关键字修饰的变量是被禁止重排序的。
1private static volatile Singleton instance = null;
EnumSingleton 枚举方式实现的单例
1// Enum Singleton
2public enum EnumSingleton {
3
4 INSTANCE;
5
6 public static EnumSingleton getInstance() {
7 return INSTANCE;
8 }
9}
InnerClassSingleton 内部类方式实现的单例
1// Inner class Singleton
2public class InnerSingleton {
3
4 private InnerSingleton(){
5 if (null != InnerHolder.INSTANCE) {
6 throw new RuntimeException("Just only one Instance can be create!");
7 }
8 }
9
10 //Serializable
11 private Object readResolve() {
12 return InnerHolder.INSTANCE;
13 }
14
15 public static InnerSingleton getInstance() {
16 return InnerHolder.INSTANCE;
17 }
18
19 private static class InnerHolder {
20 private static final InnerSingleton INSTANCE = new InnerSingleton();
21 }
22}
特别说明
- 为了避免序列化出现的 Singleton 实例不唯一的情况,覆盖 Object 的readResolve方法。
1private Object readResolve() {
2 return InnerHolder.INSTANCE;
3 }
- 为了避免,通过反射错误的使用 Singleton,在构造中做再处理
1 private InnerSingleton(){
2 if (null != InnerHolder.INSTANCE) {
3 throw new RuntimeException("Just only one Instance can be create!");
4 }
5}