定义:单例模式属于创建型模式,该类负责创建自己的对象实例,并且确保只有单个对象被创建,同时该类提供了一种全局访问其唯一实例对象的方式;这个定义中有三个要点:1、单例类只能有一个实例;2、单例类必须自己创建自己的唯一实例;3、单例类必须可以给其他所有对象提供这一唯一实例;
意图:保证一个类仅有一个实例,并提供一个访问它的全局节点;
主要解决:一个全局使用的对象的频繁地创建和销毁;
何时使用:想控制类的数目,节省系统资源的时候;
如何解决:判断系统是否已经有这个实例,有则直接返回,没有则创建返回;
关键代码:构造函数私有;
应用实例:
1、一个班级只有一个班主任;
2、Windows是多进程多线程的操作系统,在操作一个文件的时候,就不可避免地出现多个线程或者多个进程同时操作一个文件的现象,所有的文件处理必须通过一个唯一的实例来进行处理;
优点:
1、内存中只有相关对象的一个实例,减少了内存开销,尤其是频繁地创建和销毁的实例;
2、避免了对资源的多重占用;
缺点:
1、没有接口,不能继承,与单一职责冲突,一个类应该只关心内部逻辑,而不应该关系外部怎么来实例化它;
使用场景:
1、要求生产唯一序列号;
2、WEB中的计数器,不用每次刷新都在数据库中更新一次,用单例先缓存起来;
3、创建一个对象需要消耗过多的系统资源的时候,比如I/O和数据库连接等等;
单例模式的几种常见写法:
以上的四种写法大部分精力都在致力于保证多线程并发下的线程安全;因为构造方法都是私有的,也可以避免由反射获取对象实例( 在未开启忽略权限检查的情况下);但是还没有解决由于序列化和反序列化导致获取的不是同一个实例的问题,那么怎么解决呢?
答案是:在单例类中增加一个readResolve()方法,返回这个唯一的实例,就可以解决这个问题啦,这是因为反序列化的readObject()底层会做个判断,假如当前反序列化的目标对象有ReadResolve()方法,那么会调用目标类的这个方法返回一个实例对象。
最后一种单例写法,通过枚举来实现单例,这种比较推荐,这种写法天然就是线程安全的,所以我们就不需要花费大量的精力来保证线程安全,同时既可以防止反序列化生成不同实例,又可以防止反射生成不同实例:
为什么既可以避免反射生成不同实例,又可以避免反序列化生成不同实例呢?下面我们一一道来:
1、至于为什么通过反射不能生成实例对象呢?请看下这个枚举类反编译之后的代码,同时还包含一个匿名内部类:
仔细分析这个枚举反编译之后的代码,枚举类的enum其实是个关键字,普通的枚举类其实都是一个继承了Enum的普通的java子类,所有的枚举类都具有这个公共的父类,列子中的枚举类也包含了两个静态属性SPRING和ENUM$VALUES,这两个静态属性的初始化是在静态块中进行的,类加载执行静态块中的代码时候会初始化给这两个静态属性赋值,而类加载的过程会调用ClassLoader类的loadClass()方法,这个方法的方法体是用synchronized修饰的,必定是线程安全的,所以我们说这种写法是线程安全的原因在这,并且这两个静态属性还是final修饰的,一旦初始化完成则不允许修改,初始化完成之后我们的SPRING =new Singleton(“SPRING”, 0),又因为我们这个枚举类中包含了抽象方法,根据java规范,抽象方法只能存在于接口或者抽象方法中,所以这个子类反编译之后必然是用abstract修饰的抽象类,众所周知抽象类是不能被实例化的,并且这两个类中没有定义无参构造方法,所以不能被反射实例化了。
2、为什么能防止反序列化生成多个对象呢(当然我们说序列化的前提是类都实现了序列化接口)?
首先要从枚举类的序列化说起,枚举类在序列化的时候其实只是将Singleton(key,value)中的key进行了序列化,而反序列化的时候也是通过这个key去对应的map中获取对应的Singleton(key,value)实例的,大致就是这么个逻辑,有兴趣可以翻翻源码看看。
Original: https://www.cnblogs.com/wha6239/p/16635112.html
Author: 一只烤鸭朝北走
Title: 设计模式之(4)——单例模式
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/612882/
转载文章受原作者版权保护。转载请注明原作者出处!