学习笔记-设计模式之单例模式

本文内容源于视频教程,若有侵权,请联系作者删除。

 

一、定义

 单例模式(Singleton Pattern)是指确保一个类在任何情况 下都绝对只有一个实例,并提供一个全局访问点。

二、特点

1.构造方法私有

2.有一个全局访问点

三、举例

以太阳为例

1.饿汉式

 1 public class SunHungrySingleton {
 2     // 1.普通加载
 3     private static final SunHungrySingleton sunHungrySingleton = new SunHungrySingleton();
 4     // 2.通过静代码块加载
 5 //    static {
 6 //        sunHungrySingleton = new SunHungrySingleton();
 7 //    }
 8     private SunHungrySingleton() {
 9     }
10 
11     private SunHungrySingleton getInstance(){
12         return sunHungrySingleton;
13     }
14 }

饿汉式在类初始化时就已经加载实例,缺点是浪费资源。由此出现了懒汉式。

2.懒汉式

 1 public class SunLazySingleTon {
 2     private static SunLazySingleTon lazySingleTon;
 3 
 4     private SunLazySingleTon() {
 5     }
 6     
 7     public SunLazySingleTon getInstance(){
 8         if (null == lazySingleTon){
 9             lazySingleTon = new SunLazySingleTon();
10         }
11         return lazySingleTon;
12     }
13 }

顾名思义,懒汉式只有在用到的时候才会实例化对话,节省了内存资源,但也引发的新的问题:线程安全。下面继续优化。

优化方法一:加上synchronized关键字

 1 public class SunLazySingleTon {
 2     private static SunLazySingleTon lazySingleTon;
 3 
 4     private SunLazySingleTon() {
 5     }
 6 
 7     public synchronized SunLazySingleTon getInstance(){
 8         if (null == lazySingleTon){
 9             lazySingleTon = new SunLazySingleTon();
10         }
11         return lazySingleTon;
12     }
13 }

虽然jdk1.6之后对synchronized优化了不少,但是依然会产生性能问题,下面继续优化。

优化方法二:双重检查

 1 public class SunLazyDoubleCheckSingleTon {
 2     private static SunLazyDoubleCheckSingleTon lazySingleTon;
 3 
 4     private SunLazyDoubleCheckSingleTon() {
 5     }
 6 
 7     public SunLazyDoubleCheckSingleTon getInstance(){
 8         if (null == lazySingleTon){
 9             synchronized (SunLazyDoubleCheckSingleTon.class) {
10                 if (null == lazySingleTon) {
11                     lazySingleTon = new SunLazyDoubleCheckSingleTon();
12                 }
13             }
14         }
15         return lazySingleTon;
16     }
17 }

这就是double check,第8行和第10行各有一处判断,若实例已被创建好,在第8行会直接过滤掉,不会进入synchronized方法。若实例在没有被初始化的情况下,多个线程同时进入第9行,在第10行会过滤掉。

这样看起来似乎是万无一失了,实则不然,当实例未初始化时,两个线程同时访问时,可能会出现其中一个返回空的情况。原因是对象的赋值操作并不是原子性的,它包含以下步骤:

① 分配内存给对象

② 实例化对象

③ 将对象和内存地址建立关联

在jvm执行以上操作时,可能不会按照顺序执行,这就是指令重排序。当第一个线程执行到第8行,第二个线程执行到第11行,且由于执行重排线程二把当步骤②和③交换时,会发生上述情况。

此时,volatile闪亮登场。

 1 public class SunLazyDoubleCheckSingleTon {
 2     private volatile static SunLazyDoubleCheckSingleTon lazySingleTon;
 3 
 4     private SunLazyDoubleCheckSingleTon() {
 5     }
 6 
 7     public SunLazyDoubleCheckSingleTon getInstance(){
 8         if (null == lazySingleTon){
 9             synchronized (SunLazyDoubleCheckSingleTon.class) {
10                 if (null == lazySingleTon) {
11                     lazySingleTon = new SunLazyDoubleCheckSingleTon();
12                 }
13             }
14         }
15         return lazySingleTon;
16     }
17 }

然而,不管怎么优化,synchronized关键字依然存在。有没有更好的方法呢,请继续往下看。

 1 public class SunLazyInnerClassSingleTon {
 2     
 3     private SunLazyInnerClassSingleTon() {
 4     }
 5 
 6     public static SunLazyInnerClassSingleTon getInstance(){
 7         return LazyHolder.LAZY;
 8     }
 9 
10     private static class LazyHolder{
11         private static final SunLazyInnerClassSingleTon LAZY = new SunLazyInnerClassSingleTon();
12     }
13 }

该方法巧妙的利用了类加载机制实现了单例。

 

然而,单例模式到这里并没有结束,通过反射,序列化依然可以破坏单例,有兴趣的读者可以自行研究。

posted @ 2020-07-04 17:56  落雨有清·风  阅读(113)  评论(0编辑  收藏  举报