点击上方“JAVA”,星标公众号重磅干货,第一时间送达

  • 饱汉模式
  • 饿汉模式
  • Holder模式
  • 枚举模式
  • 丑陋但好用的语法糖
  • 总结

“你知道茴香豆的‘茴’字有几种写法吗?”

纠结单例模式有几种写法有用吗?有点用,面试中经常选择其中一种或几种写法作为话头,考查设计模式和coding style的同时,还很容易扩展到其他问题。

这里讲解几种笔者常用的写法,但切忌生搬硬套,去记“茴香豆的写法”。编程最大的乐趣在于“know everything, control everything”。

JDK版本:oracle java 1.8.0_102

大体可分为4类,下面分别介绍他们的基本形式、变种及特点。

饱汉模式

饱汉是变种最多的单例模式。我们从饱汉出发,通过其变种逐渐了解实现单例模式时需要关注的问题。

基础的饱汉

饱汉,即已经吃饱,不着急再吃,饿的时候再吃。所以他就先不初始化单例,等第一次使用的时候再初始化,即“懒加载”。

// 饱汉// UnThreadSafepublic class Singleton1 {  private static Singleton1 singleton = null;  private Singleton1() {  }  public static Singleton1 getInstance() {    if (singleton == null) {      singleton = new Singleton1();    }    return singleton;  }}

饱汉模式的核心就是懒加载。好处是更启动速度快、节省资源,一直到实例被第一次访问,才需要初始化单例;小坏处是写起来麻烦,大坏处是线程不安全,if语句存在竞态条件。

写起来麻烦不是大问题,可读性好啊。因此,单线程环境下,基础饱汉是笔者最喜欢的写法。但多线程环境下,基础饱汉就彻底不可用了。下面的几种变种都在试图解决基础饱汉线程不安全的问题。

饱汉 - 变种 1

最粗暴的犯法是用synchronized关键字修饰getInstance()方法,这样能达到绝对的线程安全。

// 饱汉// ThreadSafepublic class Singleton1_1 {  private static Singleton1_1 singleton = null;  private Singleton1_1() {  }  public synchronized static Singleton1_1 getInstance() {    if (singleton == null) {      singleton = new Singleton1_1();    }    return singleton;  }}

变种1的好处是写起来简单,且绝对线程安全;坏处是并发性能极差,事实上完全退化到了串行。单例只需要初始化一次,但就算初始化以后,synchronized的锁也无法避开,从而getInstance()完全变成了串行操作。性能不敏感的场景建议使用。

饱汉 - 变种 2

变种2是“臭名昭著”的DCL 1.0。

针对变种1中单例初始化后锁仍然无法避开的问题,变种2在变种1的外层又套了一层check,加上synchronized内层的check,即所谓“双重检查锁”(Double Check Lock,简称DCL)。

// 饱汉// UnThreadSafepublic class Singleton1_2 {  private static Singleton1_2 singleton = null;

  public int f1 = 1;   // 触发部分初始化问题  public int f2 = 2;  private Singleton1_2() {  }  public static Singleton1_2 getInstance() {    // may get half object    if (singleton == null) {      synchronized (Singleton1_2.class) {        if (singleton == null) {          singleton = new Singleton1_2();        }      }    }    return singleton;  }}

变种2的核心是DCL,看起来变种2似乎已经达到了理想的效果:懒加载+线程安全。可惜的是,正如注释中所说,DCL仍然是线程不安全的,由于指令重排序,你可能会得到“半个对象”,即”部分初始化“问题。详细在看完变种3后,可参考下面这篇文章,这里不再赘述。

https://monkeysayhi.github.io/2016/11/29/volatile关键字的作用、原理/

饱汉 - 变种 3

变种3专门针对变种2,可谓DCL 2.0。

针对变种3的“半个对象”问题,变种3在instance上增加了volatile关键字,原理见上述参考。

// 饱汉// ThreadSafepublic class Singleton1_3 {  private static volatile Singleton1_3 singleton = null;

  public int f1 = 1;   // 触发部分初始化问题  public int f2 = 2;  private Singleton1_3() {  }  public static Singleton1_3 getInstance() {    if (singleton == null) {      synchronized (Singleton1_3.class) {        // must be a complete instance        if (singleton == null) {          singleton = new Singleton1_3();        }      }    }    return singleton;  }}

多线程环境下,变种3更适用于性能敏感的场景。但后面我们将了解到,就算是线程安全的,还有一些办法能破坏单例。

当然,还有很多方式,能通过与volatile类似的方式防止部分初始化。读者可自行阅读内存屏障相关内容,但面试时不建议主动装逼。

饿汉模式

与饱汉相对,饿汉很饿,只想着尽早吃到。所以他就在最早的时机,即类加载时初始化单例,以后访问时直接返回即可。

// 饿汉// ThreadSafepublic class Singleton2 {  private static final Singleton2 singleton = new Singleton2();  private Singleton2() {  }  public static Singleton2 getInstance() {    return singleton;  }}

饿汉的好处是天生的线程安全(得益于类加载机制),写起来超级简单,使用时没有延迟;坏处是有可能造成资源浪费(如果类加载后就一直不使用单例的话)。

值得注意的时,单线程环境下,饿汉与饱汉在性能上没什么差别;但多线程环境下,由于饱汉需要加锁,饿汉的性能反而更优。

Holder模式

我们既希望利用饿汉模式中静态变量的方便和线程安全;又希望通过懒加载规避资源浪费。Holder模式满足了这两点要求:核心仍然是静态变量,足够方便和线程安全;通过静态的Holder类持有真正实例,间接实现了懒加载。

// Holder模式// ThreadSafepublic class Singleton3 {  private static class SingletonHolder {    private static final Singleton3 singleton = new Singleton3();    private SingletonHolder() {    }  }  private Singleton3() {  }

  /**  * 勘误:多写了个synchronized。。  public synchronized static Singleton3 getInstance() {    return SingletonHolder.singleton;  }  */  public static Singleton3 getInstance() {    return SingletonHolder.singleton;  }}

相对于饿汉模式,Holder模式仅增加了一个静态内部类的成本,与饱汉的变种3效果相当(略优),都是比较受欢迎的实现方式。同样建议考虑。

枚举模式

用枚举实现单例模式,相当好用,但可读性是不存在的。

基础的枚举

将枚举的静态成员变量作为单例的实例:

// 枚举// ThreadSafepublic enum Singleton4 {  SINGLETON;}

代码量比饿汉模式更少。但用户只能直接访问实例Singleton4.SINGLETON——事实上,这样的访问方式作为单例使用也是恰当的,只是牺牲了静态工厂方法的优点,如无法实现懒加载。

丑陋但好用的语法糖

Java的枚举是一个“丑陋但好用的语法糖”。

枚举型单例模式的本质

通过反编译打开语法糖,就看到了枚举类型的本质,简化如下:

// 枚举// ThreadSafepublic class Singleton4 extends Enum<Singleton4> {  ...  public static final Singleton4 SINGLETON = new Singleton4();  ...}

本质上和饿汉模式相同,区别仅在于公有的静态成员变量。

用枚举实现一些trick

这一部分与单例没什么关系,可以跳过。如果选择阅读也请认清这样的事实:虽然枚举相当灵活,但如何恰当的使用枚举有一定难度。一个足够简单的典型例子是TimeUnit类,建议有时间耐心阅读。

上面已经看到,枚举型单例的本质仍然是一个普通的类。实际上,我们可以在枚举型型单例上增加任何普通类可以完成的功能。要点在于枚举实例的初始化,可以理解为实例化了一个匿名内部类。为了更明显,我们在Singleton4_1中定义一个普通的私有成员变量,一个普通的公有成员方法,和一个公有的抽象成员方法,如下:

// 枚举// ThreadSafepublic enum Singleton4_1 {  SINGLETON("enum is the easiest singleton pattern, but not the most readable") {    public void testAbsMethod() {      print();      System.out.println("enum is ugly, but so flexible to make lots of trick");    }  };  private String comment = null;  Singleton4_1(String comment) {    this.comment = comment;  }  public void print() {    System.out.println("comment=" + comment);  }  abstract public void testAbsMethod();  public static Singleton4_1 getInstance() {    return SINGLETON;  }}

这样,枚举类Singleton4_1中的每一个枚举实例不仅继承了父类Singleton4_1的成员方法print(),还必须实现父类Singleton4_1的抽象成员方法testAbsMethod()。

总结

上面的分析都忽略了反射和序列化的问题。通过反射或序列化,我们仍然能够访问到私有构造器,创建新的实例破坏单例模式。此时,只有枚举模式能天然防范这一问题。反射和序列化笔者还不太了解,但基本原理并不难,可以在其他模式上手动实现。

下面继续忽略反射和序列化的问题,做个总结回味一下:


单例模式是面试中的常考点,写起来非常简单。

来源:monkeysayhi.github.io

--END--

▐ 推荐

【大数据】教程 完整版

【JAVA】从零基础到精通完整版视频 附源码+工具+笔记文档 IDEA版

【Web前端】WEB前端全栈工程师开发 附完整视频+源码+工具

【软件测试】从零基础到软件测试工程师 附完整视频+工具+面试简历

IntelliJ IDEA 2020.2 EAP 5 发布:完美支持Java 15

面试:说说Java中的 volatile 关键词?

公众号ID|javabaiwen

小编微信|619531440

每天分享技术干货

视频 | 电子书 | 面试题 | 开发经验

使用严格模式的坏处_再见面试官:单例模式有几种写法?相关推荐

  1. xxl子任务_阿里面试官:聊一下分布式任务调度有那些解决方案?

    作者:黄兆平 来源:http://blog.freshfood.cn/article/39 # 简介 随着系统规模的发展,定时任务数量日益增多,任务也变得越来越复杂,尤其是在分布式环境下,存在多个业务 ...

  2. 单例模式双重校验锁_被面试官虐过之后,他轻蔑的问我:你还说你了解单例模式吗?...

    单例,大家肯定都不陌生,这是Java中很重要的一个设计模式.其实单例模式看上去简单,实际上却有很多容易被忽视的地方,因为他涉及到一些线程安全的问题,稍不留神就可能入坑. 本文,就通过一次面试经历来深入 ...

  3. eureka自我保护时间_阿里面试官问我:到底知不知道什么是Eureka,这次,我没沉默...

    文章首发:阿里面试官问我:到底知不知道什么是Eureka,这次,我没沉默 什么是服务注册? 首先我们来了解下,服务注册.服务发现和服务注册中心的之间的关系. 举个形象的例子,三者之间的关系就好像是供货 ...

  4. wcf afterreceiverequest获取body数据_阿里面试官的灵魂拷问:究竟如何保证API接口数据安全?...

    前言 前后端分离的开发方式,我们以接口为标准来进行推动,定义好接口,各自开发自己的功能,最后进行联调整合.无论是开发原生的APP还是webapp还是PC端的软件,只要是前后端分离的模式,就避免不了调用 ...

  5. Python代码列主元消去法matlab编程_当面试官要求现场手敲代码,如何体现你对Python的编程能力?...

    如果你已经通过了招聘人员的电话面试,那么下面正是该展现你代码能力的时候了.无论是练习,作业,还是现场白板面试,这都是你证明自己的代码技巧的时刻.我们知道面试官常常会出一些题让你来解决,作为一名程序员, ...

  6. 程序员多数性功能不行_从面试官角度观察到的程序员技能瓶颈,同时给出突破瓶颈的建议...

    我在做技术面试官的时候,面试对象大多数是初级开发和高级开发,偶尔也会面试架构师.我发现,IT人的工资固然和相关技术的使用年限有关,但如果候选人的技能无法跨越到更高层次的话,他们的收入也就会停留在当前级 ...

  7. 岗位理解_当面试官问求职者对应聘岗位的理解情况

    虽然求职是看的是岗责是否匹配自己的经历和能力,但是很多面试官还是会问这样的问题.对于求职者来说,对岗位的理解其实就是把岗位职责背一遍,但是面试官既然问了,就要说出一些其他内容来.想回答好这个问题,就要 ...

  8. Mysql实现幂等_阿里面试官:接口的幂等性怎么设计?

    大家好,我是狂聊. 自己最近负责的几个接口,都涉及到了幂等性的操作,抽空总结了一下,这也是面试官比较爱问的问题. 一.什么是幂等? 看一下维基百科怎么说的: 幂等性:多次调用方法或者接口不会改变业务状 ...

  9. hashmap存多少条数据_干货 | 面试官想问的HashMap,都在这一篇里面了!

    来源公众号:非科班的科班 本文思维导图 HashMap简介 HashMap 是很常用的一种集合框架,其底层实现方式在 JDK 1.7和 JDK 1.8中却有很大区别.HashMap 是用来存储数据的, ...

最新文章

  1. [Spark][Hive][Python][SQL]Spark 读取Hive表的小例子
  2. win7 php 5.3,win7 64位 WAMP环境下(PHP5.3) redis扩展无法生效
  3. linux date抖动,一场由fork引发的超时,让我们重新探讨了Redis的抖动问题
  4. 深度学习技术在机器阅读理解应用的研究进展
  5. c语言二叉树的头文件叫什么,西安交大朱站立数据结构——使用C语言》头文件系列——二叉树.doc...
  6. 互联网晚报 | 8月31日 星期二 | 荣耀深圳全球总部正式启用;长安马自达收购一汽马自达尘埃落定;浙江推出“浙江外卖在线”...
  7. 手写一切(updating...)
  8. QT制作自定义进度条(圆环状)
  9. B1105 Spiral Matrix (画图)
  10. 【计组笔记-1】ASCII码,区位码,国标码,汉字内码,以及万国码
  11. 计算机无法安装u盘驱动,Win7提示“无法成功安装设备驱动程序”,解决无法安装USB驱动程序的问题!...
  12. Javascript面试题 把AaaBbbcccc 变成AaaBbbCcc
  13. 高并发系统中库存热点的解决方案
  14. java 密码库_JCA-Java加密框架
  15. 迁移学习(Transfer Learning)的背景、历史
  16. Python案例篇3-pip install 失败 Command “python setup.py egg_info“ failed with error code 1
  17. 百度地图离线API2.0(含示例,可完全断网访问)
  18. 常用工具类 Math:数学计算 Random:生成伪随机数 SecureRandom:生成安全的随机数 2020-2-13
  19. wordpress 文章php,Wordpress制作文章页面single.php
  20. 计算机图形学中的光栅化

热门文章

  1. mysql索引数据结构图解_MySQL索引底层结构与实现原理
  2. yum update upgrade 区别_CentOS与RedHat的区别
  3. [科研论文]基于W7100的以太网读卡器的设计与实现
  4. Android仿人人客户端(v5.7.1)——网络模块处理的架构
  5. 使用PHP开发你必须得注意的要点
  6. DC.SetMapMode()的用法记录
  7. php 组,PHP: 子组(子模式) - Manual
  8. hanlp和jieba等六大中文分工具的测试对比
  9. React 折腾记 - (1) React Router V4 和antd侧边栏的正确关联及动态title的实现
  10. 通过脚本下派WsusAgent3.0.exe