前言

单例模式是一种应用很广泛的设计模式,提起关于他的问题,我们可以讨论到懒汉、饿汉、synchronized锁,volatile、以及如何做双重判断,以至于最后我们写出来的单例是这样的:

public class TestSingleton{

private  volatile  static TestSingleton  testSingleton =null;

public  static  TestSingleton getInstance(){

if (testSingleton==null){

synchronized (TestSingleton.class){

if (testSingleton==null){

testSingleton =new TestSingleton();

}

}

}

return testSingleton;

}

private  TestSingleton(){}

}

又或者在为了优化性能,而使用静态内部类的方式,但是我们今天不讨论于此,而是讨论单例的破坏,破坏就是想尽办法创建出让原本应该单例的对象的多个不同实例,这真是一种无聊的的情况...

反射破坏

反射作为Java中的一个强大功能,可以直接暴力的new对象,即使对象的构造方法是private的,但建无妨,所以如果构造方法没有做特殊处理的话,这就会导致生成新的实例,就像下面这样,输出结果肯定是false。

public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException{

Constructor declaredConstructor = TestSingleton.class.getDeclaredConstructor();

declaredConstructor.setAccessible(true);

TestSingleton testSingleton = declaredConstructor.newInstance();

System.out.println(testSingleton ==TestSingleton.getInstance());//false

}

对此,我们肯定需要解决,要防止一些别有用心的人。

解决方式可以利用静态类的特性来处理,当使用TestSingleton的时候,默认会先加载内部类,我们只要在构造方法中判断实例是不是不为空,不为空说明已经加载了,则抛出异常。

public class TestSingleton  implements Serializable{

public static TestSingleton getInstance(){

return TestSingletonHolder.test;

}

private  TestSingleton(){

if (TestSingletonHolder.test!=null){

throw  new RuntimeException("不允许重复创建");

}

}

private  static  class TestSingletonHolder{

private static final   TestSingleton test= new TestSingleton();

}

}

Exception in thread "main" java.lang.reflect.InvocationTargetException

at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)

at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)

at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)

at java.lang.reflect.Constructor.newInstance(Constructor.java:423)

at Main.main(Main.java:22)

Caused by: java.lang.RuntimeException: 不允许重复创建

at TestSingleton.(TestSingleton.java:11)

... 5 more

序列化破坏

有没有过这样的场景,一个单例模式创建好后,需要将对象序列化后写入到磁盘,下次再从磁盘中反序列化,转换成对象,但是问题就在于,反序列化后的对象会重新分配内存,重新创建,这就违背了单例的初衷。

public class Main extends JFrame{

public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException{

ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("TestSingleton.obj"));

objectOutputStream.writeObject(TestSingleton.getInstance());

ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("TestSingleton.obj"));

TestSingleton testSingleton = (TestSingleton) objectInputStream.readObject();

System.out.println(testSingleton);

System.out.println(TestSingleton.getInstance());

}

}

输出如下,可以看到反序列化后的对象和getInstance()是两个不同的对象。

TestSingleton@b4c966a

TestSingleton@7ef20235

那么这个问题改如何解决?即保证反序列化后的对象和getInstance()是同一个呢?

其实很简单,只要在单例类中加个readResolve()方法即可。

public  Object readResolve(){

return TestSingletonHolder.test;

}

重新运行后就会得到两个相同的对象。

TestSingleton@7ef20235

TestSingleton@7ef20235

所以,readResolve()是何方圣神,居然有如此神奇之处?。

readResolve()解析

先来盲猜一波,会不会是readObject()读取的时候,发现对象中有一个"协议"方法,这个"协议"方法就是readResolve(),他会阻止readObject()继续返回默认创建的对象,转手调用并返回此对象readResolve()的值,而readResolve()的值就是我们真正的单例对象的值。

按照JDK的源码风格,那么在他的源码中,可能会有一个方法,用来判断readResolve()这个方法存不存在,命名可能是hasReadResolve、ExistReadResolve等方式,那么我们直接在ObjectInputStream的源码中搜索一下,看看有没有。

果不其然,搜索到了下面的代码,其中有段调用了hasReadResolveMethod(),看名字就知道,这是用来判断有没有ReadResolve方法的。

至于readOrdinaryObject方法,是ObjectInputStream.readObject()一层层调用到这里的。当被反序列化的类中存在readResolve()方法时,就会走进这个if代码块,他会通过反射调用readObject()方法,并返回此方法返回的对象。但是在前一步还是先会创建新的对象,接着才判断有没有readResolve()方法,如果有,把变量obj替换成readResolve()返回的对象,如果没有,就会返回新创建的。

private Object readOrdinaryObject(boolean unshared)

throws IOException{

if (obj != null &&

handles.lookupException(passHandle) == null &&

desc.hasReadResolveMethod())

{

Object rep = desc.invokeReadResolve(obj);

if (unshared && rep.getClass().isArray()) {

rep = cloneArray(rep);

}

if (rep != obj) {

// Filter the replacement object

if (rep != null) {

if (rep.getClass().isArray()) {

filterCheck(rep.getClass(), Array.getLength(rep));

} else {

filterCheck(rep.getClass(), -1);

}

}

handles.setObject(passHandle, obj = rep);

}

}

}

另外在看下hasReadResolveMethod()方法,他直接返回了readResolveMethod是不是为空,那么就意味着,在某处会从这个类中通过反射获取这个类的readResolve()方法。

boolean hasReadResolveMethod(){

requireInitialized();

return (readResolveMethod != null);

}

所以,初始化readResolveMethod的地方在ObjectStreamClass的构造方法中,构造方法的cl参数就是被反序列化的Class对象。

private ObjectStreamClass(final Class> cl){

....

readResolveMethod = getInheritableMethod(

cl, "readResolve", null, Object.class);

}

所以总结一句话概括readResolve方法就是:允许一个类替换从流中读取的对象,然后将其返回给调用方。通过实现readResolve方法,类可以直接控制自己反序列化的实例的类型。

- END -

单例模式java概括_单例模式中总有几个歪门邪道的问题要知道!相关推荐

  1. 单例模式 java 实现_单例模式

    我们知道单例模式,其实就是返回一个被调用类的实例. 在频繁的进行实例(Instance)创建过程,难免过多的进行new InstanceName():我们可以只通过调用一个方法解决. 在进行设计模式的 ...

  2. java 单例模式双重检索_单例模式:为什么要双重检测

    http://blog.sina.com.cn/s/blog_6b6468720100kpif.html 3.3  延迟加载的思想 单例模式的懒汉式实现方式体现了延迟加载的思想,什么是延迟加载呢? 通 ...

  3. java单例模式恶汉模式_单例模式之懒汉模式恶汉模式

    单例模式,其实就是对于一个类,只能新建一个对象,不能有多个重复的对象.这样使得在程序在运行时,比如日志加载时能找到唯一的对象,以至正确匹配.就类似于一山不能有二虎一样.主要的思想其实就是运用stati ...

  4. java商务英语_商务英语中如何表达“也许”

    商务英语中如何表达"也许" "也许"和"可能"这两个词有多少种英语表达方法呢?以下是小编为大家搜索整理的商务英语中如何表达"也许& ...

  5. java 经济学_经济学中的囚徒困境

    经济学中的囚徒困境 答:集体理性与个人理性 中国大学MOOC: 呼吸系领域内在全世界影响最大,历史最长的期刊为: 答:AJRCCM 健全人格是心理健康的基础. 答:对 常用止血方法 ? 答:1.指压止 ...

  6. 外循环java作用_“双循环”中的外循环意味着什么?

    内循环是指自产自销,外循环则指的是进口与出口. 中国国际经济交流中心副理事长黄奇帆在9月12日召开的中国金融四十人"曲江论坛"上谈到"双循环"形势下" ...

  7. 设计模式-单例模式-注册式单例模式-枚举式单例模式和容器式单例模式在Java中的使用示例

    场景 设计模式-单例模式-饿汉式单例模式.懒汉式单例模式.静态内部类在Java中的使用示例: 设计模式-单例模式-饿汉式单例模式.懒汉式单例模式.静态内部类在Java中的使用示例_霸道流氓气质的博客- ...

  8. java 静态内部类 线程安全问题_单例模式的七种写法, 面试题:线程安全的单例模式...

    http://cantellow.iteye.com/blog/838473 http://meizhi.iteye.com/blog/537563 第一种(懒汉,线程不安全): Java代码  pu ...

  9. java单例模式 三种_三种java单例模式概述

    在java语言的应用程序中,一个类Class只有一个实例存在,这是由java单例模式实现的.Java单例模式是一种常用的软件设计模式,java单例模式分三种:懒汉式单例.饿汉式单例.登记式单例三种.下 ...

最新文章

  1. ISME:病原菌介导植物根际有益微生物群落组装
  2. 学JS的心路历程 -非同步执行
  3. 神策数据独家丨100% 还原小仙炖 618 私域个性化服务实践
  4. HDU - 4253 Two Famous Companies(二分+最小生成树)
  5. 开关怎么使用_开关也能自发电?ebelong易百珑S2自发电无线开关曝光
  6. JAVA ReentrantLock 分析
  7. pythonstdin_python 笔试输入:sys.stdin.readline和input
  8. 读《大道至简》第四章有感
  9. 使用ajax怎么解决乱码问题,一句话解决AJAX中文乱码问题[推荐]
  10. 超全面整理,Selenium 八大元素定位方式,(建议收藏反复使用)
  11. [机器学习] 混淆矩阵和kappa系数
  12. uvalive3983Robtruck
  13. Javascript:json数据根据某一个字段进行排序
  14. 《计算机组成原理》唐朔飞--期末复习资料
  15. nx531j android版本,努比亚Z11(NX531J)安卓6.0 魅族Flyme6.7.12.29R刷机包 紫火版 20180108更新...
  16. win10的键盘肆无忌惮的乱输入某个字母
  17. 怎么把线稿提取出来_如何提取线稿为你所用?不会的来!
  18. python求两个数的最大公约数和最小公倍数_Python求两个数最大公约数、最小公倍数...
  19. error怎么开机 fan_电脑开机黑屏提示CPU Fan Error怎么解决?
  20. postgresql Count estimate

热门文章

  1. php 配置文件设置时区_PHP中设置时区方法
  2. sslcontext java_java – SSLContext初始化
  3. Android判断手机是否是小米MIUI系统
  4. 拼多多员工匿名发帖被辞退,拼多多回应
  5. 手推向量投影长度、投影向量
  6. payjs 源码_GitHub - wlijie/payjs_test: PAYJS 小程序支付框架与示例
  7. 《云计算架构技术与实践》连载(2):1.2 云计算的发展趋势
  8. 男士保持肌肉的六窍门
  9. iVMS-4200 Vs区别_68790红单足球预测 法甲 21:00 安格斯 VS 梅斯
  10. 8个电脑小技巧让你在朋友圈秒杀众人!