【CSDN 编者按】六种模式演绎单例模式,看完你还觉得它简单吗?

作者 | 刘钊,中国农业银行研发中心,高级工程师

责编 | 欧阳姝黎

出品 | CSDN(ID:CSDNnews)

看到标题大概率有同学不服,心里想单例模式不是最简单的设计模式吗?学习单例模式之前我是这么认为的,学习之后我发现我有些“轻敌”了。单例模式设计方法之多,涉及知识之深入,是之前不曾料到的。单例模式的“别有洞天”,称得上是简约不简单,低调奢华有内涵。

单例模式又名单件模式或单态模式。英文名称为:Singleten Pattern。单例模式用于创建在软件系统中独一无二的对象。单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在生活中,单例的场景有一个学校只有一名正校长,如果出现了多个正校长,那就乱了套了。在系统建设中,单例模式的应用就太广了,从网站计数器、打印机任务队列、数据库连接类等。单例模式除了可以消除混乱,还可以减少系统资源的消耗,是一种非常常用的设计模式。

在这里我介绍了6种单例模式实现方法,每种方法都有自己的特点,让我们一起领略一下他们的风采。

单例模式第一式:饿汉式

这是大家见得最多的,实现起来最简单的单例实现方式,被各种设计模式教材收录。饿汉式其实还有一个名字叫预加载技术。也就是说系统启动后,单例对象就已经创建完毕,无论是否以后会被使用。源码示例如下:

这里就涉及一个知识点,所有的静态成员在类加载完成之后都已经完成了初始化赋值的操作。也就是EagerSingleton类的私有构造函数会被调用,实例会被创建。

再提醒大家特别注意一个小细节,EagerSingleton的构造函数,是private修饰的。这样做的一个很重要的目的,就是防止使用new关键字来创建一个新的实例,造成“多例”并存的局面。几乎所有的单例模式,均是如此,后面就不再着重说明了。

别小看饿汉式单例,他的优点可不少,除了实现难度低,容易理解之外,其实他还有线程安全的特点,虽然启动时耗费了初始化的工作,但是在高并发场景下,为系统提供了一种非常可靠的方式。饿汉式单例不仅应用广泛,也是被推荐使用的方式之一。

单例模式第二式:懒汉式

懒汉式单例模式也是非常经典的一种单例模式。懒汉式单例在类加载时不进行实例化,而是使用者第一次调用getInstance方法时进行实例化。这种延迟加载技术可以提高系统启动速度,是系统开发中一种常见的资源利用效率提高方法。源码示例如下:

虽然懒汉模式在资源利用率上有一定优势,但是懒汉模式在高并发场景,也就是多线程场景下,遇到的问题也比较多,必须加以了解和重视。为了防止多个线程同时调用getInstance方法,需要在该方法前面增加关键字synchronized进行线程访问锁定。但是这样就引出了新的问题,在高并发场景下,每次调用都进行线程访问锁定判断,会对系统性能产生较大的负面影响。

也就是说,上面这种懒汉式单例实现方法,虽然是线程安全的,但是对于高并发场景并不友好,一般不建议使用。

单例模式第三式:双重检查锁定式

针对以上懒汉式存在的问题,有一个方法可以解决,那就是使用synchronized关键字,只锁定部分代码。如下所示:

问题看起来得以解决,但实际这个方法是有线程安全问题的,是不可以用于生产场景特别是高并发场景的。假设有两个线程A和B,同时通过了instance对象为null的判断,因为有synchronized加锁机制,所以线程A先进入了synchronized锁定的代码中,进行实例创建。线程B处于排队等待状态。线程A执行完毕后,线程B进入锁定代码,这时线程B并不知道实例已经被创建,会再次创建新的实例,发生了“多例”的情况。

所以在synchronized锁定代码中,需要再次进行是否为null检查。这种方法叫做双重检查锁定(Double-Check Locking)。完整代码如下:

单例模式第四式:静态内部类实现式

下面要介绍的这种实现方式,既没有饿汉式单例无论是否使用都要占用内存的问题,也不存在上述懒汉式单例性能问题和线程安全控制复杂的问题。可谓是Java语言下一种非常好的单例模式实现方法。各位同学不要激动,大家先看一下源码示例:

是不是脑瓜子嗡嗡的?反正笔者在第一次看得时候,是看不太懂的。

让我简单解释一下,首先大家看一下代码里面是有一个静态内部类的,而真正的实例变量,是定义在这个静态内部类中的。各位细心的同学可能有疑问了,实例变量是被static修饰的,那应该是被预加载才对,怎么这个实现方法说是懒加载呢?这里有个小知识点就是内部类前面加static关键字,表示的是类级内部类,类级内部类只有在使用时才会被加载。

那又有优秀的同学来问了,既然是懒加载了,那么就可能遇到高并发场景,如何保证线程安全呢?简单说就是静态变量的初始化是由JVM保证线程安全的,具体的细节就不展开讲了,有兴趣的同学可以自行深入了解一下。

总结一下,当getInstance方法第一次被调用的时候,它第一次读取InstanceHolder.instance,导致InstanceHolder类得到初始化。而InstanceHolder类在装载并被初始化的时候,会初始化它的静态域,从而创建HolderSingleton的实例。由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。这个模式的优势在于,getInstance方法并没有做线程同步控制,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。

相对于双重检查锁定式,本方法实现单独更低,如果是需要延迟加载,更推荐使用此方法。硬要给这种实现方式找个缺点的话,那就是需要编程语言的支持。

单例模式第五式:枚举式

下面要介绍的这种方式,让人看了直喊666,陷入计算机的神奇世界久久不能自拔,直接上源码示例震撼一下:

没错,就是这么简约(但是不简单)。大体介绍一下原理,首先创建Enum时,编译器会自动为我们生成一个继承自java.lang.Enum的类,枚举成员声明中被static和final所修饰,根据在静态内部类式单例中讲过的,虚拟机会保证这个成员在多线程环境中被正确的加锁和同步,所以是线程安全的。类似于如下效果:

另外,Enum的构造方法本身就是private限制的,所以也防止了使用new关键字创建新实例。从Enum类的声明中我们也可以看出,Enum是提供了序列化的支持的,在某些需要序列化的场景下,提供了非常大的便利。另一个重要功能就是反序列化仍然可以保证对象在虚拟机范围内是单例的。

总之,借用Joshua Bloch在《Effective Java》中的一句话:单元素的枚举类型已经成为实现单例模式的最佳方法。

单例模式第六式:单例注册表式

提起单例注册表方式,大部分同学可能没有怎么听说过,可能觉得这是哪里来的“偏方”?但是如果我说这种方式是被大名鼎鼎的Spring框架使用,是不是你们就觉得有意思了。还是让我们来领略一下单例注册表的风采:

虽然大家接触这种模式比较少,代码看起来比其他方式要多一些,但是实现原理还是比较容易理解的。就是以一个HashMap来存储目前已生成的类的实例,如果可以根据类名找到对象,就返回这个对象,不再创建新对象。如果找不到,就利用反射机制创建一个,并加入到Map中。以上只是一个示意代码,作为Spring核心理念IoC的重要部分,单例注册表在Spring中的源码要复杂的多,也做了很多性能上的优化,有兴趣的同事可以去看一下Spring中AbstractBeanFactory类的源码。

不知道大家学完以上几种单例模式之后有什么感觉,这些不同的实现方式,如果要认真学习,彻底搞明白的话,涉及的知识点还是非常多的。有很多知识也不是我们平时做业务研发能接触到的。

怎么样,单例模式也是可以很炫酷的。

预约《大咖来了》直播,赢纪念版卫衣以及保温杯等礼品,在直播间精选留言提问题,若问题被采纳,将直接赠送马克杯!先到先得!

☞左手代码,右手带娃,还能发十几篇 paper,程序员女神是如何炼成的?
☞MIPS 已死,转身投靠 RISC-V!
☞没有特斯拉的 3·15 都曝了些什么?
☞如何以出售开源软件为生?

简约不简单的单例模式相关推荐

  1. HTML5+CSS3小实例:简约不简单的社交分享按钮

    HTML5+CSS3做一组简约不简单的社交分享按钮,字体图标库用到的是 font-awesome,鼠标悬停,图标变小,边框出现,然后边框倾斜并加粗,过程伴随动画,就这样,大功告成,是不是很简单? 效果 ...

  2. HTML圆和圆角柜形的制作,圆角柜——简约不简单

    原标题:圆角柜--简约不简单 圆角柜上窄下宽,具有度的设计造型,让柜子的结构更加坚固.因此,国外常称它为"A字柜",南方多称它为"大小头".圆角柜,在造型设计上 ...

  3. 简约不简单:高级时钟插件Advanced Clock Widget Pro

    今天分享的是一款非常简约和小巧的桌面时钟,可谓是非常不错! 高级时钟插件Advanced Clock Widget Pro是一款安卓平台的桌面数字时间插件,软件本身非常小巧,只有60K左右,它除了样式 ...

  4. 实现简约不简单的vuex

    关于vuex的使用,大家自然不陌生,如果有不熟练的可以多看看vuex官网,记住一条原则:异步操作以及复杂的逻辑必须通过action实现,否则会报下列错误 Error: [vuex] do not mu ...

  5. 重识 SQLite,简约不简单

    作者 | 董旭阳       责编 | 欧阳姝黎 出品 | CSDN博客 ????业精于勤,荒于嬉:行成于思,毁于随.--韩愈 大家好!我是只谈技术不剪发的 Tony 老师. 如果问你哪个数据库产品是 ...

  6. 简约不简单 细说专票电子化的“四个新”

    Hi~您好!杨斯维特又和您见面啦! 前几天,我们一起聊了聊"为什么专票电子化与你有关?""你真的了解专票电子化吗?" 接下来,我们透过"四新" ...

  7. 简约不简单的宝藏工具,每一款都非常实用

    推荐一:ImgUpscaler 它是一个特别有用的在线图像无损放大工具.它的主要作用是将图片放大无损,使图片更清晰: 它不需要注册和登录,操作非常简单,上传或拖动图片,支持批量处理图片,点击开始按钮开 ...

  8. Hexo主题模版推荐简约大方美丽的主题搭建教程

    Quiet 简约大方给人一种简单明了,干干净净的感觉 简约不简单,有层次感,可用于写技术方面的干货 和UI设计等 加载速度很快因为够简约,但是该有的功能都有了(个人认为) 页面没有显的特别的乱 品牌树 ...

  9. 从Android界面开发谈起(转)

    原文地址:http://blog.csdn.net/nieweilin/article/details/5967815 这篇文章没有打算有一个很好的逻辑去介绍android的某个方面,全盘大致上就是我 ...

最新文章

  1. 2022年,PyTorch在AI顶会的占比已经上80%了
  2. 使用OpenCV与sklearn实现基于词袋模型的图像分类预测与搜索
  3. Python for in 问题
  4. 简单检测CDN链接是否有效
  5. Android 高效的`InjectView – ButterKnife`
  6. 高等数学下-赵立军-北京大学出版社-题解-练习9.3
  7. hadoop-09-安装资源上传
  8. Flutter、ReactNative、uniapp对比
  9. 再见Spring Security!推荐一款功能强大的权限认证框架,用起来够优雅!
  10. poj 2376 bzoj 3389: [Usaco2004 Dec]Cleaning Shifts安排值班(贪心)
  11. python抓取网站内容_python抓取网站内容详细
  12. 初一年级上学期计算机课知识总结,七年级信息技术课程上册.doc
  13. 想要自己开发App难吗?教你快速生成App
  14. C语言编程>第二十三周 ③ 下列给定程序中,函数fun的功能是:利用插入排序法对字符串中的字符按从小到大的顺序进行排序。插入法的基本算法是:先对字符串中的头两个元素进行排序;然后把第三字符插入
  15. Windows自带压缩文件工具makecab命令详解
  16. MyBatis 配置 settings 标签
  17. 网络计算机记住用户名,电脑网站怎样设置记住账号密码怎么办
  18. 【大数据处理技术】实验4
  19. Windows系统DNS部署与安全
  20. 题目 1566: 贪吃的大嘴

热门文章

  1. ResNest网络系列
  2. 【java】各种对文件,读写的方法及小例子
  3. androidstudio 3 Android Device Monitor
  4. NOIP 2000 进制转换
  5. 杭电--1009 C语言实现
  6. Firefox-常用扩展
  7. Hadoop与分布式开发
  8. 自己动手开发编译器特别篇——用词法分析器解决背诵圣经问题
  9. Sql Server中判断日志是否为一个星期
  10. UML/ROSE学习笔记系列二:UML的概念模型