设计模式之 - 单例模式

本贴最后更新于 1660 天前,其中的信息可能已经时移世易

设计模式详解。

《Design Patterns: Elements of Reusable Object-Oriented Software》(即《设计模式》一书),由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 合著(Addison-Wesley,1995)。这几位作者常被称为"四人组(Gang of Four)"。

设计模式不是一种规定,也不需要为了设计模式而设计,错误的应用设计模式反而会让代码变得复杂,难以维护。相反,正确的应用设计模式,可以让我们的代码更加健壮,扩展性更好,逻辑也更加清晰。

设计模式一书针对不同的应用场景,列出了各自的最佳实践。随着技术的不断发展,可能一些范例已经不再适用,但是这本书带给我们思想上的指引,意义是非常重大的。设计模式一书,根据应用场景,大致分为建造型、结构型、行为型,总共 23 种,并提出了 6 大原则。

本文开始,试着详细介绍每种设计模式,有解释不到位,理解存在偏差的地方,请提出宝贵意见。

建造型-单例模式

单例模式 Singleton,即一个实例,类本身提供获取全局唯一的实例的方式,其它人使用的时候不需要重新创建对象,只需要通过类获取实例即可。

应用场景:配置类、工厂类等

关键点:

1、将实例变成成员变量。 2、将默认构造方法改为private。

优点:

1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。 2、避免对资源的多重占用(比如写文件操作)。

实现方式:分为懒汉式 Lazy ,饿汉式,下面依次介绍


01

饿汉式

**
**

所谓饿汉式,即程序从硬盘加载到内存中,即实现初始化,由 JVM 实现,线程安全。

**
**

方式 1

public class Singleton01 { private static final Singleton01 INSTANCE=new Singleton01(); private Singleton01(){} public void m(){ System.out.println("hello singleton1"); } public static Singleton01 getInstance(){ return INSTANCE; } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()-> System.out.println(Singleton01.INSTANCE.hashCode())).start(); } }}

输出:

1059951119
1059951119
1059951119
1059951119
1059951119
1059951119
1059951119
1059951119
1059951119

可以看到打印的是同一个对象。

方式 2 将实例过程放到静态代码块中

public class Singleton02 { private static final Singleton02 INSTANCE; static { INSTANCE = new Singleton02(); } private Singleton02() { } public void m() {        System.out.println("hello singleton2"); } public static Singleton02 getInstance() { return INSTANCE; } public static void main(String[] args) {        for (int i = 0; i < 10; i++) { new Thread(()-> System.out.println(Singleton02.INSTANCE.hashCode())).start(); } }}

输出:

2062686098206268609820626860982062686098206268609820626860982062686098206268609820626860982062686098

方式 3 使用枚举的方式

public enum Singleton08 { INSTANCE ; public void m(){ System.out.println("hello singleton8"); } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()-> System.out.println(Singleton08.INSTANCE.hashCode())).start(); } }}

输出:

2062686098206268609820626860982062686098206268609820626860982062686098206268609820626860982062686098

方式 1 和 2,除了创建实例的过程,其余均相同,理论上没有区别,比较简单,应用也最多。方式 3 使用枚举实现,这种方式是非常安全的,不能通过反射创建实例。整体来看,饿汉式这种方式,也存在一些问题,即我们没有使用这个对象,但是已经实例化了,能不能在我使用的时候才进行实例化呢,那就是懒汉式,即懒加载。下面演示一下懒汉式实现方法。

02

懒汉式

**
**

懒汉式,即在第一次调用的时候才真正的实例化,在加载的时候不进行实例化。

方式 4

public class Singleton03 { private static Singleton03 instance; private Singleton03() { } public void m() { System.out.println("hello singleton3"); }    public static Singleton03 getInstance() { if (null == instance) { instance = new Singleton03(); } return instance; }    public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()-> System.out.println(Singleton03.getInstance().hashCode())).start(); } }}

输出:

2062686098105995111910599511191059951119105995111910599511191059951119105995111910599511191059951119

可以看到,多线程的情况下,会概率出现创建多个对象的情况,那是因为,当一个线程发现当前实例为 null,需要创建一个对象,在这个过程中另一个线程也发现实例为 null,也去创建实例,导致出现多个实例,因此我们使用方式 4 来解决。

方式 5

public class Singleton04 { private static Singleton04 instance; private Singleton04() { } public void m() { System.out.println("hello singleton4"); } public static synchronized Singleton04 getInstance() { if (null == instance) { try { instance = new Singleton04(); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } return instance; } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()-> System.out.println(Singleton04.getInstance().hashCode())).start(); } }}

输出:

1585461681158546168115854616811585461681158546168115854616811585461681158546168115854616811585461681

此方式在方法上面增加 synchronized 关键字,将方法变成同步方法,因为 synchronized 关键字使用的重量级锁,上下文切换影响效率,为了减少锁的范围,可能会想到下面的方式优化代码。

方式 6

public class Singleton05 { private static Singleton05 instance; private Singleton05() { } public static Singleton05 getInstance() { if (null == instance) { synchronized (Singleton05.class) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } instance = new Singleton05(); } } return instance; } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(() -> System.out.println(Singleton05.getInstance().hashCode())).start(); } }}

输出:

29574062727790602098586510057349981047393000410312950991585461681206268609815881478521059951119

运行发现,这样是无法实现,因为多线程运行时,速度非常快,导致多个线程同时阻塞住,而获取到锁的时候就会去创建实例,因此会产生多个实例,因此需要使用下面的方式。

方式 7:DCL 方式(Double Check Locking)

public class Singleton06 { private static volatile Singleton06 instance; private Singleton06() { } public static Singleton06 getInstance() { if (null == instance) { synchronized (Singleton06.class) { if (null == instance) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } instance = new Singleton06(); } } } return instance; } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(() -> System.out.println(Singleton06.getInstance().hashCode())).start(); } }}

输出:

2062686098206268609820626860982062686098206268609820626860982062686098206268609820626860982062686098

此种方式是比较完美的解决方式。

双重检查的意义:线程由于无法获取锁,处于阻塞状态,当线程获取锁时,第二次检查一下是否对象创建完成,如果已创建完成,直接返回此对象即可。

DCL 加 volatile 的意义:保证线程可见,防止指令重排序

方式 8:内部类的方式

public class Singleton07 { private Singleton07() { } private static class SingletonHold{ private final static Singleton07 INSTANCE=new Singleton07(); } public static Singleton07 getInstance() { return SingletonHold.INSTANCE; } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()-> System.out.println(Singleton07.getInstance().hashCode())).start(); } }}

输出:

868202916868202916868202916868202916868202916868202916868202916868202916868202916868202916

使用内部类,可以将在类加载的时候不会实例化,但是调用的时候会进行实例化,比较好的方式,也是线程安全的。

总结:

单例模式是非常常用的设计模式,看似简单,其实这里面还是有许多值得研究学习。

设计模式不是为了生搬硬套,应该根据实际情况,灵活的运用,后面会介绍更多的设计模式和设计原则,欢迎大家一起讨论学习。

欢迎关注我们

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3201 引用 • 8217 回帖 • 2 关注
  • 设计模式

    设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

    201 引用 • 120 回帖 • 2 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
zylei21
努力做喜欢的事,做到极致 北京

推荐标签 标签

  • 程序员

    程序员是从事程序开发、程序维护的专业人员。

    589 引用 • 3528 回帖
  • 反馈

    Communication channel for makers and users.

    120 引用 • 906 回帖 • 277 关注
  • ngrok

    ngrok 是一个反向代理,通过在公共的端点和本地运行的 Web 服务器之间建立一个安全的通道。

    7 引用 • 63 回帖 • 655 关注
  • Git

    Git 是 Linux Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。

    211 引用 • 358 回帖
  • 区块链

    区块链是分布式数据存储、点对点传输、共识机制、加密算法等计算机技术的新型应用模式。所谓共识机制是区块链系统中实现不同节点之间建立信任、获取权益的数学算法 。

    92 引用 • 752 回帖 • 2 关注
  • CentOS

    CentOS(Community Enterprise Operating System)是 Linux 发行版之一,它是来自于 Red Hat Enterprise Linux 依照开放源代码规定释出的源代码所编译而成。由于出自同样的源代码,因此有些要求高度稳定的服务器以 CentOS 替代商业版的 Red Hat Enterprise Linux 使用。两者的不同在于 CentOS 并不包含封闭源代码软件。

    240 引用 • 224 回帖
  • 深度学习

    深度学习(Deep Learning)是机器学习的分支,是一种试图使用包含复杂结构或由多重非线性变换构成的多个处理层对数据进行高层抽象的算法。

    43 引用 • 44 回帖
  • Openfire

    Openfire 是开源的、基于可拓展通讯和表示协议 (XMPP)、采用 Java 编程语言开发的实时协作服务器。Openfire 的效率很高,单台服务器可支持上万并发用户。

    6 引用 • 7 回帖 • 121 关注
  • WebComponents

    Web Components 是 W3C 定义的标准,它给了前端开发者扩展浏览器标签的能力,可以方便地定制可复用组件,更好的进行模块化开发,解放了前端开发者的生产力。

    1 引用 • 9 关注
  • CodeMirror
    2 引用 • 17 回帖 • 171 关注
  • RemNote
    2 引用 • 16 回帖 • 24 关注
  • 强迫症

    强迫症(OCD)属于焦虑障碍的一种类型,是一组以强迫思维和强迫行为为主要临床表现的神经精神疾病,其特点为有意识的强迫和反强迫并存,一些毫无意义、甚至违背自己意愿的想法或冲动反反复复侵入患者的日常生活。

    15 引用 • 161 回帖
  • 京东

    京东是中国最大的自营式电商企业,2015 年第一季度在中国自营式 B2C 电商市场的占有率为 56.3%。2014 年 5 月,京东在美国纳斯达克证券交易所正式挂牌上市(股票代码:JD),是中国第一个成功赴美上市的大型综合型电商平台,与腾讯、百度等中国互联网巨头共同跻身全球前十大互联网公司排行榜。

    14 引用 • 102 回帖 • 311 关注
  • Angular

    AngularAngularJS 的新版本。

    26 引用 • 66 回帖 • 562 关注
  • sts
    2 引用 • 2 回帖 • 241 关注
  • GitHub

    GitHub 于 2008 年上线,目前,除了 Git 代码仓库托管及基本的 Web 管理界面以外,还提供了订阅、讨论组、文本渲染、在线文件编辑器、协作图谱(报表)、代码片段分享(Gist)等功能。正因为这些功能所提供的便利,又经过长期的积累,GitHub 的用户活跃度很高,在开源世界里享有深远的声望,并形成了社交化编程文化(Social Coding)。

    209 引用 • 2040 回帖
  • Kubernetes

    Kubernetes 是 Google 开源的一个容器编排引擎,它支持自动化部署、大规模可伸缩、应用容器化管理。

    118 引用 • 54 回帖 • 3 关注
  • 书籍

    宋真宗赵恒曾经说过:“书中自有黄金屋,书中自有颜如玉。”

    82 引用 • 412 回帖
  • 链书

    链书(Chainbook)是 B3log 开源社区提供的区块链纸质书交易平台,通过 B3T 实现共享激励与价值链。可将你的闲置书籍上架到链书,我们共同构建这个全新的交易平台,让闲置书籍继续发挥它的价值。

    链书社

    链书目前已经下线,也许以后还有计划重制上线。

    14 引用 • 257 回帖 • 1 关注
  • Hibernate

    Hibernate 是一个开放源代码的对象关系映射框架,它对 JDBC 进行了非常轻量级的对象封装,使得 Java 程序员可以随心所欲的使用对象编程思维来操纵数据库。

    39 引用 • 103 回帖 • 728 关注
  • 星云链

    星云链是一个开源公链,业内简单的将其称为区块链上的谷歌。其实它不仅仅是区块链搜索引擎,一个公链的所有功能,它基本都有,比如你可以用它来开发部署你的去中心化的 APP,你可以在上面编写智能合约,发送交易等等。3 分钟快速接入星云链 (NAS) 测试网

    3 引用 • 16 回帖 • 1 关注
  • 七牛云

    七牛云是国内领先的企业级公有云服务商,致力于打造以数据为核心的场景化 PaaS 服务。围绕富媒体场景,七牛先后推出了对象存储,融合 CDN 加速,数据通用处理,内容反垃圾服务,以及直播云服务等。

    29 引用 • 230 回帖 • 124 关注
  • Sym

    Sym 是一款用 Java 实现的现代化社区(论坛/BBS/社交网络/博客)系统平台。

    下一代的社区系统,为未来而构建

    524 引用 • 4601 回帖 • 709 关注
  • 导航

    各种网址链接、内容导航。

    45 引用 • 177 回帖
  • Firefox

    Mozilla Firefox 中文俗称“火狐”(正式缩写为 Fx 或 fx,非正式缩写为 FF),是一个开源的网页浏览器,使用 Gecko 排版引擎,支持多种操作系统,如 Windows、OSX 及 Linux 等。

    7 引用 • 30 回帖 • 378 关注
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3201 引用 • 8217 回帖 • 2 关注
  • Webswing

    Webswing 是一个能将任何 Swing 应用通过纯 HTML5 运行在浏览器中的 Web 服务器,详细介绍请看 将 Java Swing 应用变成 Web 应用

    1 引用 • 15 回帖 • 643 关注