设计模式之单例模式
Posted themonster
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式之单例模式相关的知识,希望对你有一定的参考价值。
“学习的路上没有捷径,我是这样认为的...”
定义:确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。
所以需要隐藏构造方法。
属于创建型模式【有待查阅】
单例模式有点:
1. 内存中只有一个实例,减少内存开销
2. 避免对资源的多重占用 【不是很理解】
3. 严格控制访问
单例模式的几种实现以及分析
1.【饿汉式】
1 public class HungrySingleton { 2 private static final HungrySingleton hungrySingleton = new HungrySingleton(); 3 4 private HungrySingleton(){ 5 throw new RuntimeException("Don‘t Create");//防止通过其他途径去实例化对象 6 } 7 public HungrySingleton getInstance(){ 8 return hungrySingleton; 9 } 10 }
特点:1. 线程安全,
2. 在类加载的时候,就会将该类实例化,即便没有使用也会加载,导致资源浪费。
2.【懒汉式】
1 public class LazySingleton { 2 private LazySingleton(){ 3 throw new RuntimeException("Don‘t Create");//防止通过其他途径去实例化对象 4 } 5 private volatile static LazySingleton lazySingleton; 6 7 public static LazySingleton getInstance(){ 8 if(lazySingleton==null) { //该层判断只是优化让所有线程进来先进行判断, 9 //如果没有该判断,当该对象已经实例化了, 10 //但是【每个】线程进来都会等待同步锁,导致效率低 11 synchronized (LazySingleton.class) { 12 if (lazySingleton == null) { //该层判断只是为了当多个线程都走过了第一个判断 13 // 第一个线程已经初始化实例了 14 //防止后来的线程再次创建 15 lazySingleton = new LazySingleton(); 16 } 17 } 18 } 19 return lazySingleton; 20 } 21 }
特点:双重校验保证了效率和线程安全
注意:使用 【volatile】关键字的原因
第13行,new LazySingleton(),new一个对象,有4个步骤
1. 分配内存空间。
2. 初始化实例。
3. 返回地址引用。
cpu的指令重排序,可能会将2.3执行顺序打乱
接下来,通过一段伪代码来看下
1 instance = new SingletonDemo();//可以分为以下3步完成(伪代码) 2 memory = allocate(); // 1.分配内存对象空间 3 instance = (memory); // 2.初始化对象 4 instance = memory; // 3.设置instance指向刚分配的内存地址,此时instance != null 5 //由于步骤2和步骤3不存在数据依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中并没有改变 6 //因此这种重排优化是允许的。 7 memory = allocate(); // 1.分配内存对象空间 8 instance = memory; // 3.设置instance指向刚分配的内存地址,此时instance != null, 9 // 【但是对象初始化没有完成,就相当于这个地址空间内没有实际的值。】 10 instance = (memory); // 2.初始化对象
volatile 可以禁止CPU指令重排序,从而避免这个问题。
3.静态内部类单例模式
1 public class LazyInnerClassSingleton { 2 private LazyInnerClassSingleton(){ 3 throw new RuntimeException("Don‘t Create");//防止通过其他途径去实例化对象 4 } 5 public static final LazyInnerClassSingleton getInstance(){ 6 return LazyHolder.LAZY; 7 } 8 private static class LazyHolder{ 9 private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton(); 10 } 11 }
特点:只有在调用getInstance的方法的同时,才会去初始化,并且只有一个线程可以活得对象的初始化锁。保证线程安全。【最优写法】
【对象初始化锁】还有待查询学习。
4. 枚举实现单例模式
1 public enum EnumSingleton { 2 INSTANCE; 3 4 public static EnumSingleton getInstance(){ 5 return INSTANCE; 6 } 7 }
特点:利用枚举的特性,让JVM来帮我们保证线程安全和单一实例的问题。除此之外,写法还特别简单。
【但是】
使用序列化,反序列化仍然可以将单例模式破坏
1 public class SingletonTest { 2 public static void main(String[] args) throws Exception{ 3 LazySingleton s1 = null; 4 LazySingleton s = LazySingleton.getInstance(); 5 FileOutputStream fos = new FileOutputStream("a.txt"); 6 ObjectOutputStream oos = new ObjectOutputStream(fos); 7 oos.writeObject(s); 8 oos.flush(); 9 oos.close(); 10 FileInputStream fis = new FileInputStream("a.txt"); 11 ObjectInputStream ois = new ObjectInputStream(fis); 12 s1 = (LazySingleton) ois.readObject(); 13 } 14 }
通过程序,s1与s的地址信息不同,证明为非单例对象
解决办法:
1 private Object readResolve(){ 2 System.out.println("read resolve"); 3 return instance; 4 }
加上此方法后,jdk中ObjectInputStream的类中有readUnshared()方法。
如果有该方法, 则会执行浅拷贝。【原因:去翻源码,还没看】
深拷贝和浅拷贝的区别,简述:
在JVM中,对象都保存在堆中,对象的引用都保存在栈中。
浅拷贝:只是赋值一个对象的引用,并指向在堆中的原对象。
深拷贝:栈中的引用和堆中的内存都赋值一份,并且新引用指向新内存。
所以,浅拷贝完全符合单例。
深拷贝,浅拷贝,以及加上readResolve方法后,为什么执行浅拷贝,以后补一下
以上是关于设计模式之单例模式的主要内容,如果未能解决你的问题,请参考以下文章