单例模式(SingletonPattern)是最简单的设计模式在Java中。它是一种创造性的设计模式。他的定义是:确保一个类只有一个实例,并提供一个全局访问点来访问它。单例模式一般体现在类声明中。单例类负责创建自己的对象,同时确保只创建单个对象。此类提供了一种直接访问其唯一对象的方法,而无需实例化此类的对象。但实际上,单例也不是完全安全的,可能会被销毁。以下是采访场景的还原。之所以讲这个话题,是因为面试官问了我很多关于单例模式的问题。是吗?”,接着是下面的对话:Q:单例模式绝对安全吗?A:(这个问题我知道,不要难倒我)不一定,其实单例也有可能被破坏?Q:哦?怎么说呢?A:单例模式实际上是把构造函数从外部隐藏起来,保证用户不能主动创建对象。但是实际上我们有办法销毁他。Q:你知道有什么方法可以销毁一个对象吗?单例??答:有一个比较简单的方法,就是反射,反射破坏了单例先说一个比较常见的单例模式:importjava.io.Serializable;/***使用double-checklock的方式实现单例*/publicclassSingletonimplementsSerializable{privatevolatilestaticSingletonsingleton;privateSingleton(){}publicstaticSingletongetSingleton(){if(singleton==null){synchronized(Singleton.class){if(singleton==null){singleton=newSingleton();}}}returnssingleton;}}这种单例模式提供了一个私有类型的构造器,一般情况下nces下面,我们不能直接调用对象的私有方法。但是反射技术为我们提供了一个后门。下面的代码,我们通过反射获取到Singleton的构造函数,设置它的访问权限,然后通过这个方法创建一个新的对象:importjava.lang.reflect.Constructor;publicclassSingletonTest{publicstaticvoidmain(String[]args){Si??ngletonsingleton=Singleton.getSingleton();try{ClasssingleClass=(Class)Class.forName("com.dev.interview.Singleton");构造函数constructor=singleClass.getDeclaredConstructor(null);构造函数。setAccessible(true);SingletonsingletonByReflect=constructor.newInstance();System.out.println("singleton:"+singleton);System.out.println("singletonByReflect:"+singletonByReflect);System.out.println("singleton==singletonByReflect:"+(singleton==singletonByReflect));}catch(Exceptione){e.printStackTrace();}}}输出为:singleton:com.dev.interview.Singleton@55d56113singletonByReflect:com.dev.interview。singleton@148080bbsingleton==singletonByReflect:false如上,launch可以获得一个新的单例对象,销毁单例。Q:有没有办法避免这种单例的破坏?A:其实是可以的,只要我们在构造函数中加入一些判断即可。通过下面的方式,我们在Singleton的构造函数中加入如下代码:反射,会抛出一个异常:Causedby:java.lang.RuntimeException:Singletonconstructoriscalled...序列化破坏了单例Q:嗯,挺好的,换个问题吧。A:(这部分面试官问我问题有点犹豫,我主动提醒他)其实除了反射可以毁掉单例,还有一个办法。Q:嗯,那你可以谈谈其他的方式。A:其实单例也可以通过序列化+反序列化来销毁。如下面的代码,我们先将单例对象序列化并保存到临时文件中,然后从临时文件中反序列化:=null;try{oos=newObjectOutputStream(newFileOutputStream("tempFile"));oos.writeObject(singleton);//ReadObjfromfileFilefile=newFile("tempFile");ObjectInputStreamois=newObjectInputStream(newFileInputStream(file));Singletonsingleton(SingletonsingletonSingleton)ois.readObject();//判断是否是同一个对象singleton==singletonBySerialize:"+(singleton==singletonBySerialize));}catch(Exceptione){e.printStackTrace();}}}输出如下:singleton:com.dev.interview.Singleton@617faa95singletonBySerialize:com.dev.interview.Singleton@5d76b067singleton==singletonBySerialize:false同上,先序列化再反序列化得到一个新的单例对象,即销毁单例。因为在对象反序列化的过程中,序列化会通过反射调用无参构造函数来创建一个新的对象,所以单例也可以通过反序列化来销毁。Q:有没有办法避免这种单例的破坏?答:当然有。只需修改反序列化策略即可。只需要在Sinleton中添加readResolve方法,并在该方法中指定返回对象的生成策略即可。即可以在Singleton类中加入如下代码进行序列化:privateObjectreadResolve(){returngetSingleton();}问:为什么加入readResolve可以解决序列化破坏单例的问题?A:因为反序列化过程中,在反序列化执行过程中会执行ObjectInputStream#readOrdinaryObject方法。此方法将确定对象是否包含readResolve方法。如果是,则直接调用该方法获取对象实例。Q:如果没有readResolve方法,反序列化时对象如何创建?A:当然也是反思。Q:我们之前不是提到了使用反射,直接在构造函数中抛出异常不就可以了吗?A:这个我真的试过了,但是不行。用于反序列化的反射构造函数与我们代码中使用的反射构造函数不同。用于反序列化的构造函数不会调用我们的对象。balabala里的constructor……(不知道面试官能不能看懂,感觉没看懂。。。)Q:哦。好的,你什么时候可以来上班?不久之后,我加入了这家公司。和原面试官聊天的时候,他无意中跟我说:面试你的时候,关于单例销毁的问题,其实一开始我只是随口问了一句,没想到你把我炸了20分钟...当时,我觉得你是个好人。