【趣味设计模式系列】之【单例模式】
1. 简介
单例模式(Singleton):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
2. 图解
3. 案例实现
单例特点:
- 外部类不能随便对单例类创建,故单例的
构造方法必须为private
,在类的内部自行实例化; - 提供一个
public方法入口
,作为唯一调用单例类的途径得到实例。
3.1 饿汉式
执行结果:
- 分析:类加载到内存,就实例化一个单例,通过
final
的静态变量保证唯一实例。 - 优点:线程安全。
- 缺点:不管是否用到,类装载时就完成实例化。
3.2 懒汉式
执行结果
- 分析:懒加载在调用
getInstace
方法的时候创建实例,通过100个线程测试发现其hashcode
并不相等,并不是单例。 - 优点:改善了饿汉式中实例不用也加载的弊端。
- 缺点:引入了新的问题,线程不安全,并不能保证单例
3.3 synchronized修饰方法单例
执行结果
- 分析:通过
synchronized
关键字保证线程安全。 - 优点:线程安全,保证单例。
- 缺点:方法上加锁导致性能开销。
3.4 synchronized修饰代码块的单例
结果:
- 分析:试图通过减小同步代码块的方式提高效率,带来了线程不安全。
- 优点:减小了加锁的范围,提高了性能。
- 缺点:线程不安全,结果显示不能保证单例。
3.5 双重检测安全单例
执行结果
- 分析:第一个判空语句
if (null == LAZY_SINGLETON)
,用来检测如果内存中有单例生成以后,永不进入下面的代码,直接走return
语句返回已有的单例,第二个判空语句,保证当前线程拿到锁的前后,内存中都没有单例,才执行创建单例操作,防止中途被其他线程创建单例,进而重复创建;volatile
关键字保证在执行语句LAZY_SINGLETON = new DoubleCheckedThreadSafeSingleton()
时,可以分解为如下的3行伪代码。
上面3行伪代码中的2和3之间,可能会被重排序(在一些JIT编译器上,这种重排序是真实发生的)。2和3之间重排序之后的执行时序如下。
上面的代码在编译器运行时,可能会出现重排序 从1-2-3 排序为1-3-2,如果发生重排序,另一个并发执行的线程B就有可能在判断instance不为null。线程B接下来将访问instance所引用的对象,但此时这个对象可能还没有被A线程初始化!
- 优点:保证单例与线程安全。
- 缺点:增加了代码的复杂度。
3.6 内部静态类单例
执行结果:
- 分析:因为虚拟机加载类的时候只加载一次,并且加载外部类时不会加载内部类,只有调用getInstance方法的时候,内部类才被加载,所以内部类的静态变量也只加载一次,JVM保证了单例与线程安全。
- 优点:线程安全。
- 缺点:无。
3.7 枚举单例
- 优点:线程安全,同时保证不被反序列化,因为枚举类型没有构造方法,不能反序列化后创建对象。
- 缺点:写法优点怪异。
4. 框架源码分析
以下源码分析基于Spring5.0.6 RELEASE。DefaultSingletonBeanRegistry.class
部分源码如下.
- 第一步,从
singletonObjects
中,获取 Bean 对象。 - 第二步,若为空且当前
bean
正在创建中,则从earlySingletonObjects
中获取Bean
对象。 - 第三步,若为空且允许提前创建,则从
singletonFactories
中获取相应的ObjectFactory
对象。若不为空,则调用其ObjectFactory
的getObject(String name)
方法,创建Bean
对象,然后将其加入到earlySingletonObjects
,然后从singletonFactories
删除。
由此可见,Spring
在创建单例bean
的时候,采用的是双重检测加锁机制创建bean
的。
5. 单例与静态方法的比较
- 单例支持延迟加载,静态类第一次加载就初始化;
- 单例常驻内存,除非
JVM
退出,静态方法中的对象,会随着静态方法执行完被释放,gc
回收。
6. 应用场景
- 数据库连接池,因为频繁建立或者关闭数据库连接,损耗性能非常大,因为何用单例模式来维护,就可以大大降低这种损耗;
- 线程池,因为线程是一种稀缺资源,频繁创建线程,会导系统开销增大,线程之间的频繁切换也导致性能下降,由统一的线程池管理线程;
- 开发中常用的配置工具类,因为配置类是共享的资源;
- 日志应用,因为日志属于工享文件,一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加;
- Windows的任务管理器,多次打开只会弹出一个对话框,确保系统由一个任务管理器管理;
- 网站的计数器,如果多个,计数难以同步;
综上,单例应用在共享资源上,要么方便管理,要么节约性能,避免不必要的性能开销。
7. 总结
单例的具体写法,需要结合场景与业务要求,确认是否支持线程安全,是否支持延迟加载,单例比较简单的写法是饿汉式,唯一的不足是不支持懒加载,还有静态内部类;比较完美的写法是双重检测加锁,虽然写法复杂,但支持延迟加载,线程安全,也是Spring源码使用的方式。
__EOF__

本文作者:小猪爸爸
本文链接:https://www.cnblogs.com/father-of-little-pig/p/12304306.html
关于博主:不要为了技术而技术,总结分享技术,感恩点滴生活!
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
本文链接:https://www.cnblogs.com/father-of-little-pig/p/12304306.html
关于博主:不要为了技术而技术,总结分享技术,感恩点滴生活!
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY