关于我们 | 联系我们

火狐app|火狐体育全站app

当前位置:主页 > 产品展示 > 产品四类 >

面试官问单例设计模式,问问你自己真的相识吗?小单例,不简朴

本文摘要:哇塞,被称为Java中最简朴的设计模式——单例设计模式。这都可以有万字知识点总结!开始我也不敢相信,看到后我信了!文章目录一、什么是单例设计模式?单例模式(Singleton Pattern)是 Java 中最简朴的设计模式之一。 这种类型的设计模式属于建立型模式,它提供了一种建立工具的最佳方式。这种模式涉及到一个单一的类,该类卖力建立自己的工具,同时确保只有单个工具被建立。 这个类提供了一种会见其唯一的工具的方式,可以直接会见,不需要实例化该类的工具。

火狐体育全站app

哇塞,被称为Java中最简朴的设计模式——单例设计模式。这都可以有万字知识点总结!开始我也不敢相信,看到后我信了!文章目录一、什么是单例设计模式?单例模式(Singleton Pattern)是 Java 中最简朴的设计模式之一。

这种类型的设计模式属于建立型模式,它提供了一种建立工具的最佳方式。这种模式涉及到一个单一的类,该类卖力建立自己的工具,同时确保只有单个工具被建立。

这个类提供了一种会见其唯一的工具的方式,可以直接会见,不需要实例化该类的工具。注意:单例类只能有一个实例。

单例类必须自己建立自己的唯一实例。单例类必须给所有其他工具提供这一实例。二、单例设计模式的优缺点优点:在内存中只有一个实例工具,淘汰内存开销。

解决了频繁建立和销毁内存实例工具的问题。制止过多的资源占用。好比:写文件操作。

缺点:没有接口,不能继续,与单一职责原则冲突,一个类应该只体贴内部逻辑,而不体贴外面怎么样来实例化。三、单例设计模式的使用当想要控制实例数目,节约系统资源的时候。而且在一个全局内,解决内存中频繁建立和销毁实例工具问题。四、单例设计模式分类单例设计模式的原则是建立唯一实例,可是建立唯一实例的方法有许多,也由今生成了许多种类的单例设计模式。

它们划分是:普通懒汉式单例模式、同步锁懒汉式单例模式、同步代码块懒汉式案例模式、饿汉式单例模式、双重校验锁(双检锁)单例模式、静态内部类(挂号式)单例模式和枚举单例模式五、单例模式思想的通报历程问题: 如果我们要有写单例设计模式的思想,该如何实现单例设计模式呢?怎样才气实现全局内只建立一个实例化工具并使用呢?而且在使用历程中会不会泛起其他问题呢?带着疑问先把最基础的单例设计模式写出来!首先,先建立一个类,类名为Singleton。然后去建立一个工具如下:class Singleton { Singleton instance = new Singleton();}看到这里,我们就发现这是一个普通的类,建立一个普通类的实例工具。那么我们如果实现单个实例工具的话,就不能让外界随便来会见建立该工具。

所以我们就想到了结构方法,大家知道如果类中写任何结构方法的话,它会隐式的存在一个公共的无参结构。这时候智慧的小同伴想到了私有该结构器,不让外界随便建立工具。代码如下:class Singleton { //在类的内部建立一个类的实例工具 private Singleton instance = new Singleton(); //私有化结构器,使得在类的外部不能够挪用此结构器,随意建立实例工具 private Singleton() {}}那么下一步呢?我们如作甚外界提供该类内部的实例工具呢?有的小同伴在建立类的实例工具的同时使用了修饰符static。我真的说着很智慧。

这样被static修饰了之后就可以通过类名来句点出来工具使用了?那么问题来了。如果泛起以下状况怎么办呢?看以下操作!class Singleton { //在类的内部建立一个类的实例工具,该静态修饰的工具随着类加载只建立一次实例 private static Singleton instance = new Singleton(); //私有化结构器,使得在类的外部不能够挪用此结构器,随意建立实例工具 private Singleton() {}}class Test { //划分建立了两个工具为s1和s2,而且两个工具是使用了同一个实例工具 Singleton s1 = Singleton.instance; Singleton s2 = Singleton.instance; //不信的话,你可以比力一下两个工具的地址 System.out.println(s1 == s2);//效果true,证明是同一个工具 }效果很好,那么我又来问问题了。如果建立了两个工具,这时原来的实例工具改变了,会有什么效果呢?那不就建立的两个实例工具不是同一个了嘛。

对,很对。不是同一个工具了。

来再继续看以下场景!class Test { //又划分建立了两个工具为s3和s4,这时我将原实例工具改变一下,把它置为空,会有什么效果呢? Singleton s3 = Singleton.instance; //把原实例工具置为空 Singleton.instance = null; Singleton s4 = Singleton.instance; //比力两个工具的地址 System.out.println(s1 == s2);//效果false,证明不是使用的同一个实例工具}因为上面的场景外界可以改变原来的实例工具,而造成建立实例纷歧致,那么我们就想措施限制外界更改实例。那肯定有小同伴想到使用get方法,为类提供一个get方法,将建立好的实例工具提供应外界使用就ok了。

那么get方法是外界随便就可以使用的吗,通例来说,get方法是通过建立实例工具后句点出来的,那外界建立不了实例工具我们怎么办?别忘了我们有static修饰符,加了它不就能用类名句点出来嘛。代码如下,此代码也是最终版了!//饿汉式单例模式class Singleton {//1.在类的内部建立一个类的实例,该静态修饰的工具随着类加载只建立一次实例 private static final Singleton instance = new Singleton();//2.私有化结构器,使得在类的外部不能够挪用此结构器 private Singleton() { }//3.私有化此工具,通过公共的方法来挪用//4.公共的方法,只能通过类来挪用,因为设置为static的,同时类的实例也必须为static声明的 public static Singleton getInstance() { return instance; }}细心的小同伴,会发现我在建立实例的时候不但单加了static修饰,而且还使用final修饰。这是为什么呢?其实加final是为了该工具不被改变,是代码更见结实而已!六、懒加载(Lazy Load)懒加载(Lazy Load),这里的懒加载指的是在使用实例工具的时候才会去建立实例工具。

这就制止了资源的浪费和内存的占用问题。其实懒加载无非就是在空间换时间与时间换空间中的取舍!再一次提问: 而且该实现还遗留了一个问题那就是,如果在此类中写的代码。我们不管用不用该实例工具,它类加载的时候就自动建立一个工具。

会造成资源的浪费和内存的占用。虽然占用的不多,可是也是一种毛病,懂吧!那我我们思量在单例模式思想通报历程中的终极版(此终极版就是饿汉式单例模式),它不支持懒加载。那怎样才气支持懒加载呢?那我们就需要控制建立工具不在类加载的时候建立,而是在get方法中建立实例工具为外界提供。先看代码吧,以下方法实现单例模式就支持懒加载了!//普通懒汉式单例模式(线程不宁静)class Singleton { //建立实例工具 private static Singleton instance = null; //私有化结构器 private Singleton() { } //提供static修饰的get方法,以供外界建立实例工具 public static Singleton getInstance() { //判断实例工具是否为空,为空则建立实例工具并返回 if (instance == null) { instance = new Singleton(); } return instance; }}七、单例设计模式的线程宁静问题问题: 在单例设计模式中什么是线程不宁静?单例模式中,只会建立一个实例工具,也就是外界使用的实例工具是同一个工具,固然既然是同一个他们的地址都是相同的!所谓单例设计模式中的线程不宁静,就是存在可以建立多个该实例工具的现象!问题: 普通懒汉式单例模式是怎样个线程不宁静呢?如果将其改装为线程宁静的呢?单例设计模式的线程宁静问题,继懒加载问题分析后,普通的懒汉式单例模式会存在线程宁静问题。

在单例设计模式建立实例工具是一个原子操作!它的线程不宁静,可以解释为多个线程在并发会见建立此单例工具时,同时在判空环节抢到了CUP的时间片,建立了两个或多个该实例工具。破坏了单例设计模式单实例工具原则!线程宁静的单例模式有许多,好比先容思想通报历程时的谁人饿汉式单例模式,它天生就是线程宁静的,你好好琢磨一下饿汉式,我不行能有建立多个实例的情况!线程宁静的单例模式,在第八章的分类剖析中,我会一一枚举,并将所有单例模式作对于写出他们的特点、优缺点等等!改装普通懒汉式单例模式并解决线程宁静问题如果想要改装普通懒汉式单例模式,我们就必须使用到同步锁(synchronized)了!如下两种操作可以解决普通懒汉式线程宁静问题!代码如下:1.为原子操作方法加同步锁//同步锁懒汉式单例模式(线程宁静)class Singleton { //建立实例工具 private static Singleton instance = null; //私有化结构器 private Singleton() { } //加同步锁并被static修饰的get方法 public synchronized static Singleton getInstance() { //详细来说以下是原子操作 if (instance == null) { instance = new Singleton(); } return instance; }}2.为该实例工具加同步代码块注意: 在使用同步代码块的时候,括号内不能是this。

因为我们使用static修饰建立工具,同步的工具不行能同步外界通过static构建出来的工具的,因为此操作并不合理。所以,此处写了this会飘红报错!//同步锁懒汉式单例模式(线程宁静)class Singleton { //建立实例工具 private static Singleton instance = null; //私有化结构器 private Singleton() { } //加同步锁并被static修饰的get方法 public static Singleton getInstance() { //此处锁的是实例工具 synchronized(Singleton.class) { if (instance == null) { instance = new Singleton(); } return instance; } }}八、单例模式分类剖析8.1 饿汉式单例模式(推荐)8.2 普通懒汉式单例模式8.3 同步锁懒汉式单例模式8.4 同步代码块懒汉式单例模式8.5 双重校验锁/双检锁单例模式(推荐)8.6 静态内部类/挂号式单例模式(推荐)8.7 枚举单例模式九、解决多种破坏单例模式原则的方法大家都知道,我们学过的反射技术是何等的无赖,它恰似一个年老在它眼前修饰符都是一个弟弟。

都可以使用暴力反射来突破封装、突破修饰符的限制。除此之外序列化,可以通过序列化将工具写在文件中,然后通过读取文件来建立工具。在单例设计模式中,有三种单例模式是我们推荐使用的单例模式,它们不仅效率高而且还保证了线程宁静。

划分是饿汉式单例模式 、双重锁校验单例模式 和静态内部类单例模式。虽然它们是线程宁静的,可是都可以被反射和序列化攻击,从而破坏了单例原则!(在这里我们先把枚举单例模式放一放!)在此以上反射和序列化都可以破坏单例设计模原则!那我们该怎么办?9.1 反射破坏单例模式原则剖析反射通过突破私有结构器建立实例工具反射破坏单例模式原则首先,先写一个线程宁静的单例模式,三者随便写一个,反射突破单例原则方法都是一样的!//饿汉式单例模式public class Singleton {//1.在类的内部建立一个类的实例,该静态修饰的工具随着类加载只建立一次实例 private static final Singleton instance = new Singleton(); //2.私有化结构器,使得在类的外部不能够挪用此结构器 private Singleton() { } //3.私有化此工具,通过公共的方法来挪用//4.公共的方法,只能通过类来挪用,因为设置为static的,同时类的实例也必须为static声明的 public static Singleton getInstance() { return instance; }}接下来,我们使用反射技术来突破单例原则!注意: 通过反射技术来建立单例工具的焦点,即是将其私有化结构器修饰符置为无效并通过结构器来建立实例工具public class Test { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { /** * 饿汉式单例模式获取实例工具 */ Singleton instance1 = Singleton.getInstance(); /** * 反射获取实例工具 */ //获取Singleton类中的私有结构器 Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(); //使private修饰失效,突破私有结构器 constructor.setAccessible(true); //通过类结构器来建立实例工具 Singleton instance2 = constructor.newInstance(); //判断工具是否是同一个工具 System.out.println(instance1 == instance2);//效果为false,证明两个实例工具不是同一个实例工具 }}解决反射破坏单例原则为相识决反射使用结构器来建立实例工具来突破单例原则,我们需要在反射突破结构器的时候判断是否已经建立过该实例工具,如果建立过该实例工具我们将为其抛出异常。简朴来说在私有结构器中加上一个实例工具判空的操作。

这样就能阻止反射技术突破单例原则!//饿汉式单例模式public class Singleton {//1.在类的内部建立一个类的实例,该静态修饰的工具随着类加载只建立一次实例 private static final Singleton instance = new Singleton(); //2.私有化结构器,使得在类的外部不能够挪用此结构器 private Singleton() { //防止反射突破单例原则if (null != instance) { //抛出异常提示:实例工具建立失败 throw new RuntimeException("Failed to create the instance object"); } } //3.私有化此工具,通过公共的方法来挪用//4.公共的方法,只能通过类来挪用,因为设置为static的,同时类的实例也必须为static声明的 public static Singleton getInstance() { return instance; }}这时候我们再试图使用反射技术来突破单例原则会泛起如下现象!9.2 序列化破坏单例模式原则剖析序列化通过使用ObjectOutputStream和ObjectInputStream流,把工具写入文件并读取文件建立工具序列化破坏单例模式原则首先,写一个线程宁静的单例模式,我们还是选择饿汉式单例模式,记得我们要实现序列化接口。如果不实现序列化的话,会报错的!//饿汉式单例模式public class Singleton implements Serializable { private static final Singleton instance = new Singleton(); private Singleton() { //防止反射突破单例原则if (null != instance) { //抛出异常提示:实例工具建立失败 throw new RuntimeException("Failed to create the instance object"); } } public static Singleton getInstance() { return instance; }}接下来,我们将该饿汉式单例模式使用序列化突破一下。public class Test { public static void main(String[] args) throws IOException, ClassNotFoundException { /** * 饿汉式单例模式获取实例工具 */ Singleton instance1 = Singleton.getInstance(); /** * 通过序列化获取实例工具 */ //建立ObjectOutputStream工具、FileOutputStream工具并建立写入obj.obj文件 ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("obj.obj")); //将instance1写入obj.obj文件中 objectOutputStream.writeObject(instance1); //关闭流 objectOutputStream.close(); //建立ObjectInputStream工具、FileInputStream工具并指定读出obj.obj文件 ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("obj.obj")); //将工具从obj.obj文件中读出并形成了instance2实例工具 Object instance2 = objectInputStream.readObject(); //关闭流 objectInputStream.close(); //比力两个工具是否为同一个工具 System.out.println(instance1 == instance2);//false,证明两个工具不是同一个工具 }}以上操作可见,我们解决反射的方式不能解决序列化破坏单例模式。分析序列化破坏单例原则解决序列化破坏单例原则,我们就需要相识一下底层原理啦。

然后我们进入ObjectInputStream的底层看一下,并找到private Object readOrdinaryObject(boolean unshared)方法。在这里建立对可以明白为ObjectInputStream的readObject()返回工具。

然后红圈内的newInstance()方法,是通过反射技术挪用无参结构建立工具。红圈左边有一个isInstantiable()方法,是判断如果serializable/externalizable的类可以在运行时被实例化,那么该方法就返回true。由此可见,我们实现了Serializable序列化接口,该方法就返回true,然后通过反射技术挪用无参结构方法建立实例工具,破坏了单例原则!解决序列化破坏单例原则既然我们在上文已经找到了是序列化是如何破坏单例原则的原因,那我们就可以凭据它来找到解决的措施。至于解决方案,我们需要在Singleton类中界说一个readResolve方法,然后在该方法中返回实例工具即可!//饿汉式单例模式public class Singleton implements Serializable { private static final Singleton instance = new Singleton(); private Singleton() { //防止反射突破单例原则if (null != instance) { throw new RuntimeException("Failed to create the instance object"); } } public static Singleton getInstance() { return instance; } //解决序列化破坏单例模式 private Object readResolve() { return instance; }}这时候,我们在使用序列化攻击后,对两个工具的地址就会返回true了,返回true效果就证明晰两个实例工具是同一个实例工具。

然后,我们分析这是怎么实现的?来继续回到ObjectInputStream源码中,继续找到谁人readOrdinaryObject方法,其中有这么些代码,如下:第一个红线框中的hasReadResolveMethod()方法代表的是如果实现 Serializable 或者 Externalizable接口的类中包罗readResolve方法,则返回效果true。第二个红线框中的invokeReadResolve()方法代表的是通过反射技术挪用要被发序列化的类的readResolve方法。底层原理也解释过了,所以可以总结为在Singleton中界说readResolve方法,并在该方法中指定要返回的工具的生成计谋,就可以防止单例被破坏。十、枚举单例模式的关键底层和攻击解决关于枚举单例模式的代码,就只是一个INSTANCE。

可是它怎么实现的,是如何制止反射和序列化攻击的,我们有待研究。10.1 反射攻击枚举单例模式/** * 枚举 */public enum Singleton { INSTANCE}public class Test { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Singleton instance1 = Singleton.INSTANCE; Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(); constructor.setAccessible(true); Singleton instance2 = constructor.newInstance(); System.out.println(instance1 == instance2); }}实验使用反射攻击,获得的效果却是一个飘红的报错信息并显示没有无参结构初始化,如下:然后我们进入Enum的源码检察,发现枚举类确实是没有无参结构。所以反射不行能从无参结构器中攻击!那么有参结构器呢?枚举类中有嘛?于是我翻了一下源码,发现枚举是提供有参结构的!那么我们就顺藤摸瓜,来使用反射攻击一下有参结构!public class Test { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Singleton instance1 = Singleton.INSTANCE; Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(String.class, int.class); constructor.setAccessible(true); Singleton instance2 = constructor.newInstance(); System.out.println(instance1 == instance2); }}效果很显着,又泛起了飘红的报错信息并显示无法以反射方式建立枚举工具 ,如下:通过反射技术来对枚举单例模式,显然是不行以建立实例工具的。所以枚举单例模式,制止的反射技术的攻击。

那么它最终是怎么实现反射有参结构也不行以建立工具的呢?那我们就需要进入反射技术的newInstance()方法中检察源代码了。看到源代码的你,是否知道了为什么不能反射枚举类建立工具了吗?是因为它对枚举做了判断,如果是枚举就会抛出异常!10.2 序列化攻击枚举单例模式/** * 枚举 */public enum Singleton { INSTANCE}public class Test { public static void main(String[] args) throws IOException, ClassNotFoundException { Singleton instance1 = Singleton.INSTANCE; ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("obj.obj")); objectOutputStream.writeObject(instance1); objectOutputStream.close(); ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("obj.obj")); Object instance2 = objectInputStream.readObject(); objectInputStream.close(); System.out.println(instance1 == instance2);//true,证明两个工具是同一个工具 }}Java规范中划定,每一个枚举类型极其界说的枚举变量在JVM中都是唯一的,因此在枚举类型的序列化和反序列化上,Java做了特殊的划定。

在序列化的时候Java仅仅是将枚举工具的name属性输出到效果中,反序列化的时候则是通过 java.lang.Enum 的 valueOf() 方法来凭据名字查找枚举工具。也就是说,以枚举为例,序列化的时候只将 INSTANCE 这个名称输出,反序列化的时候再通过这个名称,查找对应的枚举类型,因此反序列化后的实例也会和之前被序列化的工具实例相同。也就是我们看到的效果true了。

十一、表格总结注意: 除枚举单例模式以外,其他的单例模式都是可以通过我们的干预来制止反射或系列化攻击的,而且我在文章中有过详细的解说和底层分析!作者:Ziph原文链接:https://blog.csdn.net/weixin_44170221/article/details/106365623。


本文关键词:火狐全站app,面试,官问,单例,设计模式,问问,你自己,真的

本文来源:火狐app-www.hbbjjg.com

Copyright © 2006-2021 www.hbbjjg.com. 火狐app科技 版权所有 备案号:ICP备82215594号-2