设计模式之单例模式:7种单例设计模式(Java实现)

  • 单例模式
  • 饿汉式
  • 懒汉式
  • 懒汉式+同步方法
  • Double-Check
  • Double-Check+Volatile
  • Holder方式
  • 枚举方式
  • 各种单例模式的对比与总结

在经历了秋招的多场面试的毒打之后,我发现Java后端开发方向的许多面试官对设计模式都挺感兴趣。最近结合着汪文君老师的《Java高并发编程详解》书籍,对设计模式进行了一些学习,本博客主要记录一下对于单例模式的学习心得。

单例模式

单例模式是GoF23种设计模式中最常用的设计模式之一,它在日常生活中的应用有任务管理器、回收站等,特点是只能打开一个。单例模式提供了一种在多线程情况下保证实例唯一性的解决方案。
本博客记录七种单例模式的实现方法,分别是:(1)饿汉式;(2)懒汉式;(3)懒汉式+同步方法;(4)Double-Check;(5)Double-Check + Volatile;(6)Holder方法;(7)枚举方式。
为了比较这七种方式,我们可以从线程安全、高性能和懒加载这三个维度去衡量。

饿汉式

饿汉式的单例模式的代码如下:

package Chapter14.Hungry;// 饿汉式的单例模式的设计
// final不允许被继承
public final class Singleton {// 实例变量private byte[] data = new byte[1024];// 在定义实例对象的时候直接初始化private static Singleton instance = new Singleton();// 私有构造函数,不允许外部newprivate Singleton() {}public static Singleton getInstance() {return instance;}
}

其中,Singleton类是使用final修饰的,不允许被继承。data是该类的一个实例变量,占用1KB的空间。接下来的其他方式中也会存在相似的部分。
实现单例模式的重点在于:instance对象、私有构造函数和获取instance对象的方法。下面具体分析一下:
在饿汉式的单例模式下,如果主动使用了Singleton类,instance在类加载的初始化阶段就会直接完成创建,因此该方法在多线程的情况下也能保证同步,因为不可能被实例化两次,不存在线程不安全的情况。
如果一个类的成员属性比较少,且占用的内存资源不多,饿汉式的性能是比较高的(因为getInstance()方法非常简单);否则,饿汉式存在的缺点就被放大,饿汉式也不是较优的选择了。
饿汉式的主要缺点,就是饿汉式不能提供懒加载,在使用Singleton类时就创建了成员属性,一直占用着内存资源。我们更希望能实现懒加载的方式,创建类时可以没有data这些实例变量,直到使用getInstance()方法时再创建。

懒汉式

懒汉式的单例模式的代码如下:

package Chapter14.Lazy;public final class Singleton {private byte[] data = new byte[1024];private static Singleton instance = null;private Singleton() {}public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

对比一下饿汉式,我们可以发现懒汉式的主要变动就是,在类加载时,instance对象为空,直到getInstance()方法被调用,判断instance是否仍然为空,若为空才进行赋值。这种区别也体现在“饿汉式”与“懒汉式”的名称的区别上,“饿汉”看到东西就会去吃(类加载时就赋值instance),“懒汉”总是把事情拖到最后一刻(调用了getInstance()方法才去赋值instance)。
懒汉式的效能还是比较高的,也可以实现懒加载。但是上述懒汉式的代码并不能保证线程同步。问题出在getInstance()方法中:原instance为null,如果两个线程A和B,A调用getInstance(),执行instance = new Singletion()需要一定时间;在这段时间中,B也调用了getInstance(),也要去执行instance = new Singletion(),这样instance就被实例化了两次,线程不同步了。

懒汉式+同步方法

为了解决上述懒汉式设计模式中存在的问题,可以强制进行同步,即使用synchronized关键字去修饰getInstance()方法。代码如下:

package Chapter14.LazySynchronized;public final class Singleton {private byte[] data = new byte[1024];private static Singleton instance = null;private Singleton() {}public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

实际上就是在getInstance()方法前添加了synchronized关键字进行修饰。如此,同步的懒汉式可以保证线程安全,也可以实现懒加载。但是synchronized关键字天生的排他性导致了getInstance()方法在同一时刻只能被一个线程所访问,性能低下。

Double-Check

Double-Check在懒汉式的基础上,提供了一种高效的数据同步策略——首次初始化时加锁,之后则允许多个线程同步调用getInstance()方法获得实例。具体代码如下:

package Chapter14.DoubleCheck;import java.net.Socket;
import java.sql.Connection;public final class Singleton {private byte[] data = new byte[1024];private static Singleton instance = null;// conn和socket模拟其他需要初始化的实例变量Connection conn;Socket socket;private Singleton() {// 此处需要实例化conn和socket等实例变量}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}}

在这里我们设置了conn和socket实例变量,用来模拟单例模式中一些需要进行初始化设置的实例变量,比如Connection conn需要与数据库进行连接,Socket socket需要建立一个TCP连接等。接下来将会提到Double-Check的问题,就会和这些实例变量有关。
分析一下Double-Check如何实现线程安全:当两个线程A和B同时发现instance == null时,只有线程A有资格进入同步代码块,完成对instance的实例化;等到A释放同步资源后,线程B发现instance != null,就只需要去获取instance即可。
因此Double-Check方式可以保证线程安全,也很高效,同时也支持懒加载。但是Double-Check有个致命的问题,那就是可能会引发空指针异常。下面我们来分析一下为什么会发生异常。
在Singleton构造函数中,我们需要实例化instance本身,同时还要实例化conn和socket这两个资源。假设线程A第一个调用getInstance()方法,此时instance == null,因此A获取到同步资源,对instance进行加载。由于指令会被重排序,在Singleton的构造函数中,有可能instance最先被实例化,而conn和socket等资源后被实例化。在conn和socket被实例化的过程中,又有一个线程B调用了getInstance()方法,它发现instance != null,则获取到了instance,然后又去使用conn或socket,就会产生空指针异常。

Double-Check+Volatile

既然我们分析到了,产生异常的原因在于指令可能会被重排序,导致instance被实例化早于其他实例资源。因此我们可以用volatile关键字去修饰instance,并严格管理Singleton构造函数中各个资源与instance的顺序。具体代码如下:

package Chapter14.DoubleCheckVolatile;import java.net.Socket;
import java.sql.Connection;public final class Singleton {private byte[] data = new byte[1024];// 加volatile关键字保证有序性,防止指令重排序private volatile static Singleton instance = null;Connection conn;Socket socket;private Singleton() {// 先实例化conn和socket等实例变量// 最后实例化instance}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}

由此,我们实现了一种满足线程安全、高性能、懒加载的单例模式。不过这种方式用得还是比较少,更常见的是下面两种单例模式。

Holder方式

Holder方式的单例模式的代码如下:

package Chapter14.Holder;public final class Singleton {private byte[] data = new byte[1024];private Singleton() {}// 在静态内部类中持有Singleton的实例,并且可被直接初始化private static class Holder {private static Singleton instance = new Singleton();}// 调用getInstance()方法,实际上是获得Holder的instance静态属性public static Singleton getInstance() {return Holder.instance;}
}

Holder方式完全借助了类加载的特性。在Singleton类中并没有instance的静态成员变量,只有在其静态内部类Holder中有。因此,在Singleton类初始化时,并不会创建Singleton的实例。只有在getInstance()方法第一次被调用时,Singleton的实例instance才会被创建,由此实现了懒加载。另外,这个创建过程是在Java编译时期收集至<clinit>()方法(JVM的一个内部方法,在类加载的初始化阶段起作用)中的。这个方法是同步方法,保证内存的可见性、JVM指令的原子性和有序性。
因此Holder方式可以保证线程安全、高性能和懒加载,它也是单例模式中最好的设计之一,使用非常广泛。

枚举方式

枚举方式是《Effective Java》作者力推的方式,在很多优秀开源代码中经常可以看到枚举方式的例子。枚举方式的示例代码如下:

package Chapter14.Enum;public enum Singleton {INSTANCE;private byte[] data = new byte[1024];Singleton() {System.out.println("INSTANCE will br initialized immediately");}public static void method() {// 调用该方法将会主动使用Singleton, INSTANCE将会被初始化}public static Singleton getInstance() {return INSTANCE;}}

枚举方式不能被继承,只能实例化一次,同样也是线程安全,且高效的。但是枚举方式不能懒加载,第一次调用Singleton枚举时,instance就会被实例化。
可以根据Holder方式,对枚举方式进行改造,改造后的代码如下:

package Chapter14.EnumLazyLoad;public class Singleton {private byte[] data = new byte[1024];private Singleton() {}private enum EnumHolder {INSTANCE;private Singleton instance;EnumHolder() {this.instance = new Singleton();}private Singleton getSingleton() {return instance;}}public static Singleton getInstance() {return EnumHolder.INSTANCE.getSingleton();}
}

各种单例模式的对比与总结

下表展示了各种单例模式在线程安全、高性能、懒加载维度的一些比较:

线程安全 高性能 懒加载 其他
饿汉式 ×
懒汉式 ×
懒汉式+同步方法 ×
Double-Check 会引发空指针异常
Double-Check+Volatile
Holder方式
枚举方式 × 可以添加Holder方式实现懒加载
枚举方式+Holder方式

最后,《Java高并发编程详解》的作者汪文君老师指出,在实际开发中,Holder方式和枚举方式是最常见的设计单例的方式。
我们可以多多学习、应用,最终完全掌握单例模式,并进一步学习其他的设计模式。在开发中,各种设计模式还是很重要的。

设计模式之单例模式:7种单例设计模式(Java实现)相关推荐

  1. java饿汉式有啥作用,Java面试 - 什么是单例设计模式,为什么要使用单例设计模式,如何实现单例设计模式(饿汉式和懒汉式)?...

    什么是单例设计模式? 单例设计模式就是一种控制实例化对象个数的设计模式. 为什么要使用单例设计模式? 使用单例设计模式可以节省内存空间,提高性能.因为很多情况下,有些类是不需要重复产生对象的.如果重复 ...

  2. java实现一个单例设计模式_Java正确实现一个单例设计模式的示例

    Java正确实现一个单例设计模式的示例 发布于 2021-1-12| 复制链接 分享一篇关于关于Java正确实现一个单例设计模式的示例,小妖觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的 ...

  3. 单例模式示例_单例设计模式示例

    单例模式示例 本文是我们名为" Java设计模式 "的学院课程的一部分. 在本课程中,您将深入研究大量的设计模式,并了解如何在Java中实现和利用它们. 您将了解模式如此重要的原因 ...

  4. 单例设计模式介绍||单例设计模式八种方式——1) 饿汉式(静态常量) 2) 饿汉式(静态代码块) 3) 懒汉式(线程不安全) 4) 懒汉式(线程安全,同步方法)

    单例模式 单例设计模式介绍 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法). 比如Hibernate的 ...

  5. 23种设计模式:单例设计模式(饿汉式 VS 懒汉式)

    23种设计模式:单例设计模式(饿汉式 VS 懒汉式) 每博一文案 世事浮沉,有太多的责任需要我们担当,生活中总有些挫折和磨难,让我们觉得快要杠不住了. 但当我们咬牙坚持过那段难熬的时光后,发现并没有想 ...

  6. 小看--单例设计模式

    (一)单例设计描述 只要了解过设计模式的同学都会知道:单例设计模式,大家都知道单例设计模式是一种创建行的设计模式.既然是创建型,那么先来讲讲,对象的创建的过程吧. --静态成员:静态成员在程序加载的时 ...

  7. 第 5 章 单例设计模式

    第 5 章 单例设计模式 1.单例设计模式介绍 所谓类的单例设计模式, 就是采取一定的方法保证在整个的软件系统中, 对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法). ...

  8. Java设计模式——单例设计模式/权限修饰符的使用

    1. 单例模式含义 所谓的单例设计模式,就是采取一定的方法保证整个软件系统中,某个类只能存在一个对象实例. 单例设计模式 2. 单例设计模式的两种实现方法 饿汉式:不管是否需要该实例,我事先就把该实例 ...

  9. Java设计模式—单例设计模式(Singleton Pattern)完全解析

    转载请注明出处:http://blog.csdn.net/dmk877/article/details/50311791 相信大家都知道设计模式,听的最多的也应该是单例设计模式,这种模式也是在开发中用 ...

  10. 设计模式之单例设计模式

    1 设计模式(Design pattern) 代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用.设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案.这些解决方案是众多软件开发人 ...

最新文章

  1. 所有 SAP 现在开设的标准课程
  2. 信纳比(SINAD)介绍
  3. Linux2.6内核--内存管理(2)--区
  4. mysql删除数据后不释放空间问题
  5. 为什么使用中间件下载时总是收到警告消息Object is in status Wait
  6. 计算机图形学考试题及答案_计算机图形学考试题及答案
  7. 性能优化之NSDateFormatter
  8. jQuery框架学习第九天:jQuery工具函数介绍与使用
  9. vscode 新版eslint自动修复_程序员请收好:10 个实用的 VS Code 插件
  10. 在openGL中绘制图形
  11. 会php会javascript,javascript – 只会php和js但不会java,能做手机应用开发吗?
  12. 基于PlayTennis数据集的决策树决策分析
  13. android fastboot模式,fastboot
  14. 通过YYtext实现文本点击(类似微博效果)
  15. 大疆无人机实现目标定位
  16. Spring Boot 自定義 HttpMessageConverter 解決 String 類型返回JSON對象問題
  17. 【python 淘宝爬虫】淘宝信誉分抓取
  18. 在jsp页面上直接打开pdf文档
  19. 腾讯校招软件测试工程师题库
  20. 计算机可以计算出十的一百次方吗,世界上最大的数字单位 古戈尔(1古戈尔等于10的100次方)...

热门文章

  1. 云计算开发python_云计算开发学习笔记:Python的环境搭建
  2. Hello!SCDN
  3. 软件技术毕业论文编程方向
  4. 高通平台开机LOGO的修改与兼容
  5. 计算机旅游网站毕业论文,旅游网站任务书
  6. 谁说中国没有林纳斯,中国初代IT宗师的封神榜
  7. android 知识点大全 面试
  8. @JsonView注解的使用
  9. Java中的关键字有哪些?
  10. iChart--地图显示人口统计