- 建议阅读时间:5分钟。
- 更好的阅读体验最好预备知识:类加载原理(较复杂,想了解可以留言之后可以出文章)、对象的创建过程(链接地址)
单例模式
定义:内存中无或者最多只有一个该类的对象,而且是该类自己创建的该类自己的对象,创建方式唯一,访问方式唯一(如果纠结反射,那么只能用枚举单例类了,本文不考虑反射方式)。图文讲解很多地方不太好讲,大家的很多疑问在视频中会有说到。
懒汉式单例
缺点:非线程安全。
优点:懒加载,当使用的时候才加载,没有使用过就不会加载,节约内存
![8ee8847d6b02e0805661c079167d5506.png](https://i-blog.csdnimg.cn/blog_migrate/d66d37b1dd2e7ab0ead3414fdafc92a8.jpeg)
饿汉式单例
缺点:非懒加载,在类加载时就初始化,浪费内存。
优点:线程安全,在类加载的时候就进行了初始化,所以多线程不会造成什么影响。
![def3932230c97d15f1587b06d08793b3.png](https://i-blog.csdnimg.cn/blog_migrate/c62d9327da608fa46bd50de2dd1c56a9.jpeg)
DCL单例
优点:懒加载,线程安全。其实就是综合了前两种的优缺点,并且进行了优化。
缺点:有使用到同步锁,效率不如无锁方式
思考:如果不加volatile会出现什么问题?DCL可能失效,如何理解,在笔者另一篇视频:《对象是怎么创建的...》中会有线索,同时本期配套视频会有讲解。
![cc16194038fa04d42dd970467ca11a1d.png](https://i-blog.csdnimg.cn/blog_migrate/c4c43f0674f8cb5475e19876ed7ec04b.jpeg)
枚举
优点:线程安全,可以防止序列化,反射生成多例
缺点:想不出来有啥缺点
原理:JVM规定每个枚举类型以及枚举变量在JVM中都是唯一的,如果观察反编译代码会发现在序列化的时候是将对象的name属性输出到结果中,反序列化的时候是通过java.lang.Enum的valueOf()方法来根据名字查询枚举对象,前后一致,所以不存在问题。
所有自定义枚举类都继承了java.lang.Enum类,该类没有无参构造方法,只有一个有两个参数的构造方法,且程序员不能调用这个构造方法。看源码咋说的如图:
![614c12f6424f4a0013437fc8ade5611d.png](https://i-blog.csdnimg.cn/blog_migrate/497dd7a2014ad6cd7807bf0330b06df7.jpeg)
有人可能会说有参构造反射也可以调用,那么接着看图,Constructor源码如图:
![7860d65f88c467672e06c265b6bf54ad.png](https://i-blog.csdnimg.cn/blog_migrate/03dc113e5ef3aadbd984ae1f6de3415b.jpeg)
我们可以看到反射机制已经完全禁止了通过反射来生成枚举实例。到现在为止大家应该明白为什么枚举单例可以防止反射生成多例了吧,明白的赶紧点个赞和关注。
有人可能又要说序列化应该能生成多例,其实序列化这种情况和反射的处理差不多,源码ObjectInputStream中在反序列化的时候会对类型为枚举的进行单独处理,JVM规定每个枚举类型以及枚举变量在JVM中都是唯一的,如果观察反编译代码会发现在序列化的时候是将对象的name属性输出到结果中,反序列化的时候是通过java.lang.Enum的valueOf()方法来根据名字查询枚举对象,前后一致,所以不存在问题。
![ef2bb602f0cd5fea1fb68f593c9376f0.png](https://i-blog.csdnimg.cn/blog_migrate/311dc8cbfabe314c1d2da1fd3d4bffea.jpeg)
详细讲解请看视频讲解,没看懂的评论没看懂,点个关注。