学习笔记-设计模式之单例模式
本文内容源于视频教程,若有侵权,请联系作者删除。
一、定义
单例模式(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 }
该方法巧妙的利用了类加载机制实现了单例。
然而,单例模式到这里并没有结束,通过反射,序列化依然可以破坏单例,有兴趣的读者可以自行研究。