什么是单例模式?

说到单例模式,其实大家应该都不陌生,因为真的太常用了,应该所有开发者接触设计模式的第一个模式。那我这里一句话简单说下为何使用单例:如果你希望你的某个类只需要有一个实例对象,并且全局共享,那么你就使用单例。

我喜欢的单例模式实现

单例模式常见的实现有懒汉式、饿汉式这两种方式,但是在这里,我不想讨论这两种方式,因为常见所以没有讨论和需要思考的价值。

让我们来看看以下的几种方式的一些实现机制:

一、双重校验锁(DCL)

上代码:

DCL双重加锁的方式保证每次调用getSingleton方法的时候都是同步的。其实加锁大家都能理解,就是解决多线程同步的问题。但其实这里有个重点,就是这行代码:

private volatile static Singleton singleton

为什么要用volatile去修饰呢,这边从两个方面去说明:

1.如果不用volatile修饰会怎么样?

这看起来似乎也是行的通的,但是了解过编译器和程序指令的话就会知道那是不可靠的,具体原因如下:

 1)编译器优化了程序指令,以加快cpu处理速度。

 2)多核cpu会动态调整表指令顺序,以加快并行运算能力。

简单理解,那就是现在都0202年了,一台计算机cpu和内核都是好几个出现的,不在是那个单核的老时代了,所以java文件编译成字节码指令之后,你的编码逻辑确实是串行的,计算机也会根据范式把你编程的逻辑结果给你执行返回,但是具体到cpu去执行指令的时候,为了体现多核的优势,会对一些指令做并行处理,以加快程序运行速度。

我想好奇的你,还是想知道,如果不加volatile的话,会在什么时候出现问题,那我给你说说问题出现的顺序:

 1)线程A,调用方法获取实例,发现对象未实例化,准备开始实例化。

 2)由于编译器优化了程序的指令,允许对象在构造函数未调用完成前,将共享变量的引用指向部分构造的对象,虽然对象未完全实例化,但是已经不为null了。

 3)线程B进入也要调用方法获取实例,发现部分构造的对象已经不为null,则直接返回了该对象。

至于线程B返回之后会发生什么,可想而知,没实例化完,那么就会导致调用部分的方法的时候,就会有空指针的异常,所以就是我上面说的,不可靠。

2.volatile作用是啥?

为了解决这个问题,JDK1.6之后的版本提供了该关键字, 其实就是为了让其修饰的变量你能够在线程间可见,而所谓的可见,那就是大家都从主存中获取,至于主存等概念在这里就不展开说明了。

可以这么理解:在线程B读取volatile变量后,线程A在写这个volatile之前,所有可见的共享变量的值都将立即变得对线程B可见。

对应上面的问题解决也就是:线程A在未初始化完,singleton变量那就是null,线程B读到的也就是null,那么当线程B再进去想要加锁实例化的时候,发现线程A获取了锁正在实例化,那就阻塞了起来,直到A实例化完释放锁,但是因为实例化完之后B立马又知道该变量不为null了所以在第二个判断的时候,就不用进去new了,返回了。

二、静态内部类

上代码:

静态内部类是一个我比较喜欢的实现方式,当然很明显代码少,逻辑较为简单。这种方式主要是利用了classloader机制来保证初始化singleton的时候只有一个线程,避免了需要再去保证线程同步的问题。同时我们把这种方式实例化有lazy loading的效果,其实主要是因为静态内部类Holder类并不会在Singleton类被装载的时候就被初始化了,只有当Holder类被主动使用,也就是调用了getSingleton方法之后,才会显示的装载Holder类,从而实例化singleton对象。如果singleton对象是一个消耗资源占用比较大的内存的对象的时候,如果你希望延迟加载的话,那么这种方式是个不错的选择。

但是其实静态内部类的方式实际上并没有想象中的那么完美,因为它无法阻挡反射和反序列攻击,你可以利用前面两种方式再去构造新的Singleton的实例,所以不是严格意义上的单例。

三、枚举

上代码:

这种方式是Josh Bloch提倡的,利用枚举的特性,让JVM来保证线程安全和单例的问题,还能防止反序列化和反射,除了大家不怎么常用外,其实这种简单的方式是个很好的方式。

反编译看一下,其实枚举是在static块中进行的对象的创建:

单例模式真的有那么好吗?

优点:

1.提供了唯一实例的受控访问。

  2.因为只有一个实例,节约了系统资源,提高系统性能。

缺点:

      1.单例模式没有抽象层,扩展比较困难。

      2.单例类的职责过重,违背了“单一职责原则”。

我的推荐:

我们去使用单例基本目标就是为了节省内存资源,而且一般的web项目都会引入Spring框架,通过Spring实现的单例和上面设计模式说的单例有所不同。设计模式的单例是在整个Java应用中只有一个实例,而Spring中的单例是在一个IOC容器中就只有一个单例。但对于web应用来说,web容器(Jetty或tomcat)对用户的每个请求都会创建一个单独的servlet线程去处理请求,Spring框架下的接口每个action也都是单例的,那么其实就保证了我们使用的是一个实例。

同时Spring也支持我们通过注解或者xml进行lazy-init,也可以指定scope确定其是否为全局单例,又或者是多个实例,对于程序来说有了更多的选择。

当然上面提到的线程安全的问题,其实大多数情况下Spring是没有去保证所有bean的线程安全,所以主动权交给了开发者,我们自己编写程序要保证线程安全的。不过在我们经常使用的数据库dao层的那些dao 的bean对象,Spring通过ThreadLocal对象,区别与我们常用的加锁的方式而是用空间换时间,给每个线程分配了独自的变量副本,从而隔离了多线程访问对数据访问的冲突,保证了线程安全性。至于这个类和这个机制,这里就不展开谈了,谈多了这篇文章就装不下了。

点击这里,了解更多精彩内容

【华为云技术分享】【极客思考】设计模式:你确定你真的理解了单例模式吗?相关推荐

  1. 【华为云技术分享】三大前端技术(React,Vue,Angular)探密(下)

    [华为云技术分享]三大前端技术(React,Vue,Angular)探密(上) [Angular] Angular(通常被称为 "Angular 2+"或 "Angula ...

  2. 【华为云技术分享】“技术-经济范式”视角下的开源软件演进剖析-part 1

    前言 以互联网为代表的信息技术的迅猛发展对整个经济体系产生了巨大的影响.信息技术的发展一方面使知识的积累和传播更加迅速,知识爆炸性的增长:另一方面,使信息的获取变得越来越容易,信息交流的强度逐渐增加, ...

  3. 【华为云技术分享】“技术-经济范式”视角下的开源软件演进剖析-part 3

    4. 微观层面 4.1 个体动机 在开源软件发展之初, 商业组织的投入很少甚至没有, 完全是靠Richard Stallman 或者 linus Torvalds 这样的个人在努力推动开源软件艰难前行 ...

  4. 【华为云技术分享】全WEB化开发体验,开发者新利器华为云CloudIDE即将揭秘

    随着公有云的不断普及,无论是企业办公.社交网络,还是线上娱乐都越来越依赖云上提供的各种服务,微服务.DevOps.持续交付.容器化等云原生技术和理念也在企业上云大背景下不断对现有的开发活动以及开发工具 ...

  5. 【华为云技术分享】直播回顾丨激发数据裂变新动能,HDC.Cloud云数据库前沿技术解读

    3月24日14:00-17:00,HDC.Cloud开发者沙龙系列云数据库专场直播线上开启,此次华为云数据库通过三场直播从NoSQL数据库新技术.数据库迁移.行业解决方案等方面对云端数据库进行深度解读 ...

  6. 【华为云技术分享】AI 开发路漫漫,什么才是真正的极客精神?

    摘要:AI开发看上去很美,实践起来却不是一件容易的事.一个聪明的开发者知道借助工具提升开发效率,一个智能的平台则会站在开发者的立场,为用户提供贴心服务. "理想很丰满,现实很骨感." ...

  7. 【华为云技术分享】云图说 | 初识云耀云服务器,打造“极优、极简”的云上体验

    描述:华为云HECS(Hyper Elastic Cloud Server,云耀云服务器)是专为中小企业和个人开发者打造的新一代云服务器,助力企业上云更轻松! 华为云HECS(Hyper Elasti ...

  8. 【华为云技术分享】一行代码就能写一个日志打印组件,你信吗?为你揭晓LiteOS中日志打印组件的核心

    1. 做实验引发的思考 在学习LiteOS日志打印组件使用的时候,我记录了一篇博客:atiny_log | LiteOS 物联网操作系统中的日志打印组件使用分享,关于实验的具体内容,请阅读这篇博客. ...

  9. 【华为云技术分享】解析:物联网数据分析服务如何做?

    [摘要] 物联网设备正在产生大量的数据,如何为开发者提供简单有效的数据分析服务,简化开发过程,提升开发效率,让IoT数据快速变现是一个摆在我们面前的问题. 没有疑问,我们已经身处物联网时代了,每天都有 ...

  10. 【华为云技术分享】Python大神编程常用4大工具,你用过几个?

    摘要:Python是一种跨平台的编程语言,能够在所有主要的操作系统上,运行你编写的任何Python程序.今天介绍几款常见的工具:Python自带的解释器.文本编辑器(Geany.Sublime Tex ...

最新文章

  1. 「自然语言处理NLP」的“高光时刻” --- 28篇标志性论文
  2. Mako 模板系统文档翻译(1) 使用基础
  3. Linux下防止文件误删方法
  4. 记-更改配置而不需停止并重新启动服务
  5. 【动态规划】魔法石矿
  6. Vue-cli3 项目搭建和启动
  7. 《南溪的目标检测学习笔记》的笔记目录
  8. html dom子节点,HTML DOM 节点
  9. 集群提交HBase代码报错:Caused by: java.lang.ClassNotFoundException: org.apache.hadoop.hbase.HBaseConfiguratio
  10. Sublime Text各种插件使用方法
  11. linux子系统的初始化_subsys_initcall()【转】
  12. Centos7 完全卸载mysql5.7
  13. (四)、Redis删除策略---Redis设计与实现读书笔记
  14. Altium Designed导出Gerber,Gerber文件所对应的层
  15. dagger2简单使用与理解笔记
  16. 开关Switch系列:Switch修改滑块(thumb)和滑道(track)的颜色(一)
  17. Python数据分析(全) #超长预警 #思维导图 #matplotlib #numpy #pandas
  18. 如何借助大数据技术找到精准客户?
  19. Serializable transient
  20. hfs服务器不推送文件,hfs文件服务器linux

热门文章

  1. es6 方法的 name 属性
  2. 一:Tixiao Shan最新力作LVI-SAM(Lio-SAM+Vins-Mono),基于视觉-激光-惯导里程计的SLAM框架,环境搭建和跑通过程
  3. Git笔记(24) 维护项目
  4. php获取内存峰值,php内存\获取\使用
  5. python numpy array转置_Python numpy数组转置与轴变换
  6. oracle数据泵导入导出_【软件】R语言数据导入与导出
  7. 获取android com包名,Android系统中获取进程(和顶端包名)
  8. logback实践笔记
  9. jdk678910新特性地址
  10. duilib学习领悟(2)