单例模式——小白总结
设计模式(Design pattern),提供了在软件开发过程中面临的一些问题的最佳解决方案,是Java开发者必修的一门课程。主要分创建型模式、结构型模式和行为型模式。其中接下来我们要写的是单例模式,属于创建型模式。
单例模式(Singletom Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供全局访问点。
关键词:隐藏其所有的构造方法 属于创建型模式 确保任何情况下都绝对只有一个实例
应用:ServletContext、ServletConfig、ApplicationContext、DBPool
常见写法:饿汉式单例、懒汉式单例、注册式单例、ThreadLocal单例
1、饿汉式单例
类加载时就会创建。不管你用不用,都进行初始化。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w6zggRtU-1627743930051)(https://i.loli.net/2021/07/31/Kv67A95sSoaXh4m.png)]
加静态代码块的:
注意点:final关键字,可以防止通过反射机制对你私有化的构造方法覆盖。
优点:够安全,不管你是否多线程,都能保证你创建的是同一个对象。
缺点:每次初始化的时候就创建对象,如果成千上万个线程调用,会造成内存的浪费。
2、懒汉式单例
顾名思义,只有调用时才创建
1、双重检测锁
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KE42aEqU-1627743930063)(https://i.loli.net/2021/07/31/jaGBJp935PQ4gNe.png)]
说明:不在方法外面加锁,在方法内部加,也是优化性能
最外层循环的作用:过滤掉大部分线程,不走同步方法,提高
volatile关键字的作用:解决指令重排问题(2、3顺序会颠倒)
? CPU执行时会转换成JVM指令执行
? 1、分配内存给这个对象
? 2、初始化对象
? 3、将初始化好的对象和内存地址建立关联、赋值
? 4、用户初次访问
2、静态内部类
原理:巧妙利用类的加载机制:内部类先加载,LazyHolder里面的逻辑需要外部方法调用时才执行,属于JVM底层的实现逻辑。
优点:没用到synchronized,保证了效率。
缺点:和饿汉式单例一样,不能控制实例初始化。
构造方法里面加判断,避免反射攻击,其他单例也一样。
接下来我们看一下每加判断和加了判断的反射攻击
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wKLdhV7l-1627743930074)(https://i.loli.net/2021/07/31/vWo13EUtsVDcA2H.png)]
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DRsDy1zQ-1627743930079)(https://i.loli.net/2021/07/31/5yFabWui7rO2Jhp.png)]
说明:通过构造方法反射调用单例,他会得到不一样的对象,不符合单例模式。
3、注册式单例
将每一个实例都缓存到统一的容器中,使用唯一标识获取实例
1、枚举式单例(推荐)最安全
可以防止序列化攻击和反射攻击
从JDK层面就为枚举不被序列化和反射破坏做了判断
通过看他的反编译 属于饿汉式单例,安全性没得说
2、容器单例
spring中实现的单例,优点方面管理懒加载,缺点:需要加synchronized关键字,不然getBean方法不安全(如果多线程,多并发的情况下)
4、ThreadLocal单例
应用:使用ThreadLocal来实现多数据源动态切换(数据源路由)
线程伪安全,在线程里面安全
结果:
说明:他是通过对应的线程作为key,获取值,每一个key都是线程安全的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vU6c2v19-1627743930097)(https://i.loli.net/2021/07/31/3ofzkYM1OEI9XrR.png)]
5、序列化单例
测试:
不重写readObject()方法会造成两次创建对象不一致的结果
说明:他会先去判断有没有readObject方法
单例模式的优缺点:
优点:1、在内存中只有一个实例,避免了内存开销
? 2、可以避免对资源的多重占用
? 3、设置全局访问点,严格控制访问
缺点:1、没有接口,扩展困难
? 2、如果要扩展单例对象,只能修改代码,不满足开闭原则的设计思路
总结、
1、私有化构造器
2、保证线程安全
3、延迟加载
4、防止序列化和反序列化破坏单例
? 2、可以避免对资源的多重占用
? 3、设置全局访问点,严格控制访问
缺点:1、没有接口,扩展困难
? 2、如果要扩展单例对象,只能修改代码,不满足开闭原则的设计思路
总结、
1、私有化构造器
2、保证线程安全
3、延迟加载
4、防止序列化和反序列化破坏单例
5、防止反射攻击
|