单例模式我在上学期看一些资料时候学习过,没想到这学期的软件体系结构就有设计模式学习,不过看似篇幅不大,介绍得比较简单,在这里我总结下单例模式,一来整理之前的笔记,二来也算是预习复习课程了。

概述

单例模式 (Singleton Pattern) 是 Java 中最简单的设计模式之一,属于一种创建型模式。

单例模式保证对于每一个类加载器,一个类仅有一个唯一的实例对象,并提供一个全局的唯一访问点。

优缺点

优点:

  1. 减少内存开销。
  2. 避免对资源的多重占用。
  3. 严格控制客户程序访问其唯一的实例。
  4. 单例类的子类都是单例类。
  5. 比较容易改写为允许一定数目对象的类。

缺点:

  1. 不适用于变化的对象。
  2. 由于没有抽象层,难以扩展。
  3. 职责过重,一定程度上违背了“单一职责原则”。

核心思路

  1. 构造器私有化。
  2. 私有的静态常量成员。
  3. 公开的静态方法getInstance(),返回静态实例对象。
  4. 确保每次访问的实例对象都是同一个对象。
  5. 多线程中要保证线程安全。

实现方式

实现单例方式有五种:饿汉式、懒汉式、双重校验锁(DCL)、静态内部类、枚举。

饿汉式

优点:在类加载的时候就完成实例化,避免了线程同步问题。

缺点:在类装载的时候就完成实例化,没有达到 Lazy Loading 的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

  1. public class Singleton1 {
  2. private static final Singleton1 INSTANCE = new Singleton1();
  3. private Singleton1() {
  4. System.out.println("饿汉式,可用");
  5. }
  6. public static Singleton1 getInstance() {
  7. return INSTANCE;
  8. }
  9. }

懒汉式

优点:达到了懒加载的效果。

缺点:线程不安全。

  1. 对于普通懒汉式,线程不安全。

    原因:在进入if (instance == null) 时,可能一个线程进入后就切换到了另一个线程,而此时并未创建实例对象,这个线程又再次进入了if代码块。

    1. public class Singleton2 {
    2. private static Singleton2 instance;
    3. private Singleton2() {
    4. System.out.println("懒汉式,线程不安全,多线程不可用");
    5. }
    6. public static Singleton2 getInstance() {
    7. if (instance == null) {
    8. instance = new Singleton2();
    9. }
    10. return instance;
    11. }
    12. }
  2. 对方法添加synchronized保证线程安全。

    synchronized保证了getInstance()线程安全,但对方法进行同步效率不高。

    1. public class Singleton3 {
    2. private static Singleton3 instance;
    3. private Singleton3() {
    4. System.out.println("懒汉式(synchronized方法),效率太低");
    5. }
    6. public static synchronized Singleton3 getInstance() {
    7. if (instance == null) {
    8. instance = new Singleton3();
    9. }
    10. return instance;
    11. }
    12. }
  3. 改进方案2,使用synchronized代码块。

    使用了synchronized代码块改进,但又出现了和方案1一样的线程安全问题。

    1. public class Singleton4 {
    2. private static Singleton4 instance;
    3. private Singleton4() {
    4. System.out.println("懒汉式(synchronized代码块),线程不安全,多线程不可用");
    5. }
    6. public static Singleton4 getInstance() {
    7. if (instance == null) {
    8. synchronized (Singleton4.class) {
    9. // 此处若有线程阻塞,其它线程就仍可以进入到了前面if
    10. instance = new Singleton4();
    11. }
    12. }
    13. return instance;
    14. }
    15. }

双重校验锁(DCL)

针对上一个改进,使用两个if (instance == null)

这里先别管实现的Serializable接口和readResolve()方法,这两个用于后面的序列化测试。

另外,此处的volatile声明是很重要的,volatile变量有两种特性。

一是保证了此变量对所有线程的可见性。“可见性”指的是当一条线程修改了这个变量的值,新值对于其它线程来说是可以立即知道的。

二是禁止指令重排序优化。但volatile变量的运行在并发编程下并非是安全的,因为不能保证原子性。详细请自己去看相关资料。

  1. import java.io.Serializable;
  2. public class Singleton5 implements Serializable {
  3. private static final long serialVersionUID = 1L;
  4. private static volatile Singleton5 instance;
  5. private Singleton5() {
  6. System.out.println("懒汉式优化(Double Check Lock)双重校验锁,推荐用");
  7. }
  8. public static Singleton5 getInstance() {
  9. if (instance == null) {
  10. // 未被初始化,但是无法确定这时其他线程是否已经对其初始化,因此添加对象锁进行互斥
  11. synchronized (Singleton5.class) {
  12. // 再一次进行检查,因为有可能在当前线程阻塞的时候,其他线程对instance进行初始化
  13. if (instance == null) {
  14. // 此时还未被初始化的话,在这里初始化可以保证线程安全
  15. instance = new Singleton5();
  16. }
  17. }
  18. }
  19. return instance;
  20. }
  21. // 如果该对象被用于序列化,该方法可以保证对象在序列化前后保持一致
  22. private Object readResolve() {
  23. return getInstance();
  24. }
  25. }

静态内部类

静态内部类方式在类被加载时并不会立即实例化。

而是在需要实例化时,调用getInstance()方法,才会装载内部类,从而完成对象实例化。

  1. import java.io.Serializable;
  2. public class Singleton6 implements Serializable {
  3. private static final long serialVersionUID = 1L;
  4. private Singleton6() {
  5. System.out.println("饿汉式优化(静态内部类),推荐用");
  6. }
  7. private static class SingletonInstance {
  8. private static final Singleton6 INSTANCE = new Singleton6();
  9. }
  10. public static Singleton6 getInstance() {
  11. return SingletonInstance.INSTANCE;
  12. }
  13. // 如果该对象被用于序列化,该方法可以保证对象在序列化前后保持一致
  14. private Object readResolve() {
  15. return getInstance();
  16. }
  17. }

枚举

枚举方法不仅保证了线程安全问题,还提供了序列化机制。

  1. public enum Singleton7 {
  2. INSTANCE;
  3. private Singleton7() {
  4. System.out.println("枚举,最好方法");
  5. }
  6. public static Singleton7 getInstance() {
  7. return INSTANCE;
  8. }
  9. }

测试

线程安全测试

  1. public class TestSafety {
  2. static Singleton1 s1_a;
  3. static Singleton1 s1_b;
  4. static Singleton2 s2_a;
  5. static Singleton2 s2_b;
  6. static Singleton3 s3_a;
  7. static Singleton3 s3_b;
  8. static Singleton4 s4_a;
  9. static Singleton4 s4_b;
  10. static Singleton5 s5_a;
  11. static Singleton5 s5_b;
  12. static Singleton6 s6_a;
  13. static Singleton6 s6_b;
  14. static Singleton7 s7_a;
  15. static Singleton7 s7_b;
  16. public static void getS1_A() {
  17. s1_a = Singleton1.getInstance();
  18. }
  19. public static void getS1_B() {
  20. s1_b = Singleton1.getInstance();
  21. }
  22. public static void getS2_A() {
  23. s2_a = Singleton2.getInstance();
  24. }
  25. public static void getS2_B() {
  26. s2_b = Singleton2.getInstance();
  27. }
  28. public static void getS3_A() {
  29. s3_a = Singleton3.getInstance();
  30. }
  31. public static void getS3_B() {
  32. s3_b = Singleton3.getInstance();
  33. }
  34. public static void getS4_A() {
  35. s4_a = Singleton4.getInstance();
  36. }
  37. public static void getS4_B() {
  38. s4_b = Singleton4.getInstance();
  39. }
  40. public static void getS5_A() {
  41. s5_a = Singleton5.getInstance();
  42. }
  43. public static void getS5_B() {
  44. s5_b = Singleton5.getInstance();
  45. }
  46. public static void getS6_A() {
  47. s6_a = Singleton6.getInstance();
  48. }
  49. public static void getS6_B() {
  50. s6_b = Singleton6.getInstance();
  51. }
  52. public static void getS7_A() {
  53. s7_a = Singleton7.getInstance();
  54. }
  55. public static void getS7_B() {
  56. s7_b = Singleton7.getInstance();
  57. }
  58. public static void main(String[] args) {
  59. Thread t1_a = new Thread(TestSafety::getS1_A);
  60. Thread t1_b = new Thread(TestSafety::getS1_B);
  61. t1_a.start();
  62. t1_b.start();
  63. try {
  64. Thread.sleep(1000);
  65. } catch (InterruptedException e) {
  66. e.printStackTrace();
  67. }
  68. System.out.println("s1_a == s1_b ? " + (s1_a == s1_b));
  69. System.out.println();
  70. Thread t2_a = new Thread(TestSafety::getS2_A);
  71. Thread t2_b = new Thread(TestSafety::getS2_B);
  72. t2_a.start();
  73. t2_b.start();
  74. try {
  75. Thread.sleep(1000);
  76. } catch (InterruptedException e) {
  77. e.printStackTrace();
  78. }
  79. System.out.println("s2_a == s2_b ? " + (s2_a == s2_b));
  80. System.out.println();
  81. Thread t3_a = new Thread(TestSafety::getS3_A);
  82. Thread t3_b = new Thread(TestSafety::getS3_B);
  83. t3_a.start();
  84. t3_b.start();
  85. try {
  86. Thread.sleep(1000);
  87. } catch (InterruptedException e) {
  88. e.printStackTrace();
  89. }
  90. System.out.println("s3_a == s3_b ? " + (s3_a == s3_b));
  91. System.out.println();
  92. Thread t4_a = new Thread(TestSafety::getS4_A);
  93. Thread t4_b = new Thread(TestSafety::getS4_B);
  94. t4_a.start();
  95. t4_b.start();
  96. try {
  97. Thread.sleep(1000);
  98. } catch (InterruptedException e) {
  99. e.printStackTrace();
  100. }
  101. System.out.println("s4_a == s4_b ? " + (s4_a == s4_b));
  102. System.out.println();
  103. Thread t5_a = new Thread(TestSafety::getS5_A);
  104. Thread t5_b = new Thread(TestSafety::getS5_B);
  105. t5_a.start();
  106. t5_b.start();
  107. try {
  108. Thread.sleep(1000);
  109. } catch (InterruptedException e) {
  110. e.printStackTrace();
  111. }
  112. System.out.println("s5_a == s5_b ? " + (s5_a == s5_b));
  113. System.out.println();
  114. Thread t6_a = new Thread(TestSafety::getS6_A);
  115. Thread t6_b = new Thread(TestSafety::getS6_B);
  116. t6_a.start();
  117. t6_b.start();
  118. try {
  119. Thread.sleep(1000);
  120. } catch (InterruptedException e) {
  121. e.printStackTrace();
  122. }
  123. System.out.println("s6_a == s6_b ? " + (s6_a == s6_b));
  124. System.out.println();
  125. Thread t7_a = new Thread(TestSafety::getS7_A);
  126. Thread t7_b = new Thread(TestSafety::getS7_B);
  127. t7_a.start();
  128. t7_b.start();
  129. try {
  130. Thread.sleep(1000);
  131. } catch (InterruptedException e) {
  132. e.printStackTrace();
  133. }
  134. System.out.println("s7_a == s7_b ? " + (s7_a == s7_b));
  135. System.out.println();
  136. }
  137. }

输出结果:

  1. 饿汉式,可用
  2. s1_a == s1_b ? true
  3. 懒汉式,线程不安全,多线程不可用
  4. 懒汉式,线程不安全,多线程不可用
  5. s2_a == s2_b ? false
  6. 懒汉式(synchronized方法),效率太低
  7. s3_a == s3_b ? true
  8. 懒汉式(synchronized代码块),线程不安全,多线程不可用
  9. 懒汉式(synchronized代码块),线程不安全,多线程不可用
  10. s4_a == s4_b ? false
  11. 懒汉式优化(Double Check Lock)双重校验锁,推荐用
  12. s5_a == s5_b ? true
  13. 饿汉式优化(静态内部类),推荐用
  14. s6_a == s6_b ? true
  15. 枚举,最好方法
  16. s7_a == s7_b ? true

序列化测试

只对双重校验锁、静态内部类、枚举测试。

默认只有枚举能保证序列化后对象仍相等,其它需要加readResolve()方法才能。

  1. import java.io.FileInputStream;
  2. import java.io.FileOutputStream;
  3. import java.io.ObjectInputStream;
  4. import java.io.ObjectOutputStream;
  5. import java.nio.file.Path;
  6. import java.nio.file.Paths;
  7. public class TestSerializable {
  8. // 测试序列化后的对象是否相等
  9. public static void main(String[] args) {
  10. Singleton5 s5_a = Singleton5.getInstance();
  11. Singleton5 s5_b = Singleton5.getInstance();
  12. System.out.println("序列化前:s5_a == s5_b ? " + (s5_a == s5_b));
  13. Path file5 = Paths.get("object5.txt");
  14. try (ObjectOutputStream out = new ObjectOutputStream(
  15. new FileOutputStream(file5.toFile()))) {
  16. out.writeObject(s5_a);
  17. } catch (Exception e) {
  18. e.printStackTrace();
  19. }
  20. try (ObjectInputStream in = new ObjectInputStream(
  21. new FileInputStream(file5.toFile()))) {
  22. s5_b = (Singleton5) in.readObject();
  23. } catch (Exception e) {
  24. e.printStackTrace();
  25. }
  26. System.out.println("序列化后:s5_a == s5_b ? " + (s5_a == s5_b));
  27. System.out.println();
  28. Singleton6 s6_a = Singleton6.getInstance();
  29. Singleton6 s6_b = Singleton6.getInstance();
  30. System.out.println("序列化前:s6_a == s6_b ? " + (s6_a == s6_b));
  31. Path file6 = Paths.get("object6.txt");
  32. try (ObjectOutputStream out = new ObjectOutputStream(
  33. new FileOutputStream(file6.toFile()))) {
  34. out.writeObject(s6_a);
  35. } catch (Exception e) {
  36. e.printStackTrace();
  37. }
  38. try (ObjectInputStream in = new ObjectInputStream(
  39. new FileInputStream(file6.toFile()))) {
  40. s6_b = (Singleton6) in.readObject();
  41. } catch (Exception e) {
  42. e.printStackTrace();
  43. }
  44. System.out.println("序列化后:s6_a == s6_b ? " + (s6_a == s6_b));
  45. System.out.println();
  46. Singleton7 s7_a = Singleton7.getInstance();
  47. Singleton7 s7_b = Singleton7.getInstance();
  48. System.out.println("序列化前:s7_a == s7_b ? " + (s7_a == s7_b));
  49. Path file7 = Paths.get("object7.txt");
  50. try (ObjectOutputStream out = new ObjectOutputStream(
  51. new FileOutputStream(file7.toFile()))) {
  52. out.writeObject(s7_a);
  53. } catch (Exception e) {
  54. e.printStackTrace();
  55. }
  56. try (ObjectInputStream in = new ObjectInputStream(
  57. new FileInputStream(file7.toFile()))) {
  58. s7_b = (Singleton7) in.readObject();
  59. } catch (Exception e) {
  60. e.printStackTrace();
  61. }
  62. System.out.println("序列化后:s7_a == s7_b ? " + (s7_a == s7_b));
  63. System.out.println();
  64. }
  65. }

先把readResolve()方法注释掉;输出结果为:

  1. 懒汉式优化(Double Check Lock)双重校验锁,推荐用
  2. 序列化前:s5_a == s5_b ? true
  3. 序列化后:s5_a == s5_b ? false
  4. 饿汉式优化(静态内部类),推荐用
  5. 序列化前:s6_a == s6_b ? true
  6. 序列化后:s6_a == s6_b ? false
  7. 枚举,最好方法
  8. 序列化前:s7_a == s7_b ? true
  9. 序列化后:s7_a == s7_b ? true

加上readResolve()方法,枚举不需要,枚举甚至连Serializable接口都不需实现;输出结果如下:

  1. 懒汉式优化(Double Check Lock)双重校验锁,推荐用
  2. 序列化前:s5_a == s5_b ? true
  3. 序列化后:s5_a == s5_b ? true
  4. 饿汉式优化(静态内部类),推荐用
  5. 序列化前:s6_a == s6_b ? true
  6. 序列化后:s6_a == s6_b ? true
  7. 枚举,最好方法
  8. 序列化前:s7_a == s7_b ? true
  9. 序列化后:s7_a == s7_b ? true

使用场景

  • 文件管理系统
  • 日志记录类
  • 与数据库的连接

应用实例

java.lang.Runtime#getRuntime()


Java设计模式 - 单例模式(创建型模式)的更多相关文章

  1. Java设计模式之创建型模式

    创建型模式分为五类:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式 一.工厂方法模式:接口-实现类.工厂类

  2. Java设计模式_创建型模式_单例模式

    单例模式的实现: 定义一个类,在类中定义该类的静态变量,再定一个一个获取该类的静态变量的方法. UML图:

  3. [C#]设计模式-单例模式-创建型模式

    单例模式用于在整个软件系统当中保持唯一实例,在 C# 当中最能够体现此概念的就是静态类,静态类的生命周期是跟随整个程序,并且在整个程序中仅保有一个实例. 不过在这里我们不再详细阐述单例模式与静态类有什 ...

  4. Java设计模式 - - 单例模式 装饰者模式

    Java设计模式 单例模式 装饰者模式 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 静态代理模式:https://www.cnblogs.com/StanleyBlogs/p/1 ...

  5. 设计模式(Java版)-创建型模式之简单工厂模式

    前言:这段时间在学习设计模式,本人也是小菜一枚(所以写的如果有错误的地方请大大们给予指出).这个东西也是我一直想学习的,从点点滴滴做起,记录下自己每天的领悟! 一.工厂模式的动机 在软件系统中,经常面 ...

  6. GoF的23种设计模式之创建型模式的特点和分类

    创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”.这样可以降低系统的耦合度,使用者不需要关注对象的创建细节,对象的创建由相关的工厂来完成.就像我们去商场购买商品时, ...

  7. Typescript玩转设计模式 之 创建型模式

    作者简介 joey 蚂蚁金服·数据体验技术团队 前言 我们团队的工作是用单页面应用的方式实现web工具.涉及到数万到十数万行的前端代码的管理,而且项目周期长达数年. 怎么样很好地管理好这种量级的前端代 ...

  8. 单例模式——创建型模式01

    1. 名称     单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类.单例模式是一种对象创建型模式. 2. 问题    ...

  9. 设计模式01 创建型模式 - 单例模式(Singleton Pattern)

    参考 [1] 设计模式之:创建型设计模式(6种) | 博客园 [2] 单例模式的八种写法比较 | 博客园 单例模式(Singleton  Pattern) 确保一个类有且仅有一个实例,并且为客户提供一 ...

  10. Java设计模式之职责型模式总结

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6548127.html 所谓职责型模式,就是采用各种模式来分配各个类的职责. 职责型模式包括 ...

随机推荐

  1. 集合系列 List(二):ArrayList

    ArrayList 是 List 集合的列表经典实现,其底层采用定长数组实现,可以根据集合大小进行自动扩容. public class ArrayList<E> extends Abstr ...

  2. python实例:利用jieba库,分析统计金庸名著《倚天屠龙记》中人物名出现次数并排序

    本实例主要用到python的jieba库 首先当然是安装pip install jieba 这里比较关键的是如下几个步骤: 加载文本,分析文本 txt=open("C:\\Users\\Be ...

  3. (五十)c#Winform自定义控件-滑块

    前提 入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章. GitHub:https://github.com/kwwwvagaa/NetWinformControl 码云:ht ...

  4. Java 并发编程(一):摩拳擦掌

    这篇文章的标题原本叫做——Java 并发编程(一):简介,作者名叫小二.但我在接到投稿时觉得这标题不够新颖,不够吸引读者的眼球,就在发文的时候强行修改了标题(也不咋滴). 小二是一名 Java 程序员 ...

  5. react antd 关于selectedRows 的问题

    在table中,经常会用到单选和多选的功.这里会有一个方法, 当触发onchange的时候回有两个数组,[selectedRowKeys, selectedRows],当前选中的keys和每一项, 这 ...

  6. 域渗透-LSA Protection

    简介: 微软在 2014 年 3 月 12 日添加了 LSA 保护策略,用来防止对进程 lsass.exe 的代码注入,这样一来就无法使用 mimikatz 对 lsass.exe 进行注入,相关操作 ...

  7. 【故障公告】再次出现数据库 CPU 居高不下的问题以及找到问题的线索

    非常非常抱歉,今天上午的故障又一次给大家带来麻烦了,再次恳请大家的谅解. 在昨天升级阿里云 RDS SQL Server 实例的配置后(详见昨天的博文),万万没有想到,今天上午更高配置的阿里云 RDS ...

  8. 2019 Multi-University Training Contest 3

    B.Blow up the city solved by F0_0H 210min 题意 给一个DAG,每次询问给定u,v,求使得u或v不能与中心点联通的关键点个数 做法 按照拓扑序建树 新加节点的父 ...

  9. HDU 6357 Hills And Valleys

    Hills And Valleys 题意:给你一个序列, 可以翻转一次区间 [l, r] 求最大 非递减的 序列长度. 题解:枚举翻转区间,然后匹配. 如果不翻转区间, 那么就相当于用b[] = {0 ...

  10. bzoj 4025 二分图 lct

    题目传送门 题解: 首先关于二分图的性质, 就是没有奇环边. 题目其实就是让你判断每个时段之内有没有奇环. 其次 lct 只能维护树,(反正对于我这种菜鸟选手只会维护树), 那么对于一棵树来说, 填上 ...