1. 应用场景

一个无状态的类使用单例模式节省内存资源。 比如说线程池、缓存、对话框、设置偏好和注册表对象、日志对象、充当打印机、显卡等设备的驱动程序对象。

2. 概念

确保一个类只有一个实例,并提供该实例的全局访问点。

3.Class Diagram

使用一个私有构造函数、一个私有静态变量以及一个公有静态函数来实现。

私有构造函数保证了不能通过构造函数来创建对象实例,只能通过公有静态函数返回唯一的私有静态变量。

4. Implementation

4.1 懒汉式-线程不安全

以下实现中,私有静态变量 uniqueInstance 被延迟实例化,这样做的好处是,如果没有用到该类,那么就不会实例化 uniqueInstance,从而节约资源。

这个实现在多线程环境下是不安全的,如果多个线程能够同时进入 if (uniqueInstance == null) ,并且此时 uniqueInstance 为 null,那么会有多个线程执行 uniqueInstance = new Singleton(); 语句,这将导致实例化多次 uniqueInstance。

public class ChocolateBoiler {private boolean empty;private boolean boiled;private static ChocolateBoiler uniqueInstance;private ChocolateBoiler() {empty = true;boiled = false;}public static ChocolateBoiler getInstance() {if (uniqueInstance == null) {System.out.println("Creating unique instance of Chocolate Boiler");uniqueInstance = new ChocolateBoiler();}System.out.println("Returning instance of Chocolate Boiler");return uniqueInstance;}public void fill() {if (isEmpty()) {empty = false;boiled = false;// fill the boiler with a milk/chocolate mixture}}public void drain() {if (!isEmpty() && isBoiled()) {// drain the boiled milk and chocolateempty = true;}}public void boil() {if (!isEmpty() && !isBoiled()) {// bring the contents to a boilboiled = true;}}public boolean isEmpty() {return empty;}public boolean isBoiled() {return boiled;}
}

4.2 饿汉式-线程安全

线程不安全问题主要是由于 uniqueInstance 被实例化多次,采取直接实例化 uniqueInstance 的方式就不会产生线程不安全问题。

但是直接实例化的方式也丢失了延迟实例化带来的节约资源的好处。

public class ChocolateBoiler {private boolean empty;private boolean boiled;private static ChocolateBoiler uniqueInstance =new ChocolateBoiler();private ChocolateBoiler() {empty = true;boiled = false;}public ChocolateBoiler getInstance() {return uniqueInstance;}public void fill() {if (isEmpty()) {empty = false;boiled = false;// fill the boiler with a milk/chocolate mixture}}public void drain() {if (!isEmpty() && isBoiled()) {// drain the boiled milk and chocolateempty = true;}}public void boil() {if (!isEmpty() && !isBoiled()) {// bring the contents to a boilboiled = true;}}public boolean isEmpty() {return empty;}public boolean isBoiled() {return boiled;}
}

4.3 懒汉式-线程安全

只需要对 getUniqueInstance() 方法加锁,那么在一个时间点只能有一个线程能够进入该方法,从而避免了实例化多次 uniqueInstance。

但是当一个线程进入该方法之后,其它试图进入该方法的线程都必须等待,即使 uniqueInstance 已经被实例化了。这会让线程阻塞时间过长,因此该方法有性能问题,不推荐使用。

public class ChocolateBoiler {private boolean empty;private boolean boiled;private static ChocolateBoiler uniqueInstance;private ChocolateBoiler() {empty = true;boiled = false;}public synchronized static ChocolateBoiler getInstance() {if (uniqueInstance == null) {System.out.println("Creating unique instance of Chocolate Boiler");uniqueInstance = new ChocolateBoiler();}System.out.println("Returning instance of Chocolate Boiler");return uniqueInstance;}public void fill() {if (isEmpty()) {empty = false;boiled = false;// fill the boiler with a milk/chocolate mixture}}public void drain() {if (!isEmpty() && isBoiled()) {// drain the boiled milk and chocolateempty = true;}}public void boil() {if (!isEmpty() && !isBoiled()) {// bring the contents to a boilboiled = true;}}public boolean isEmpty() {return empty;}public boolean isBoiled() {return boiled;}
}

4.4 双重校验锁-线程安全

uniqueInstance 只需要被实例化一次,之后就可以直接使用了。加锁操作只需要对实例化那部分的代码进行,只有当 uniqueInstance 没有被实例化时,才需要进行加锁。

双重校验锁先判断 uniqueInstance 是否已经被实例化,如果没有被实例化,那么才对实例化语句进行加锁。

public class ChocolateBoiler {private boolean empty;private boolean boiled;private static volatile ChocolateBoiler uniqueInstance;private ChocolateBoiler() {empty = true;boiled = false;}public static ChocolateBoiler getInstance() {if (uniqueInstance == null) {synchronized (ChocolateBoiler.class){if(uniqueInstance==null){System.out.println("Creating unique instance of Chocolate Boiler");uniqueInstance = new ChocolateBoiler();}}}System.out.println("Returning instance of Chocolate Boiler");return uniqueInstance;}public void fill() {if (isEmpty()) {empty = false;boiled = false;// fill the boiler with a milk/chocolate mixture}}public void drain() {if (!isEmpty() && isBoiled()) {// drain the boiled milk and chocolateempty = true;}}public void boil() {if (!isEmpty() && !isBoiled()) {// bring the contents to a boilboiled = true;}}public boolean isEmpty() {return empty;}public boolean isBoiled() {return boiled;}
}

考虑下面的实现,也就是只使用了一个 if 语句。在 uniqueInstance == null 的情况下,如果两个线程都执行了 if 语句,那么两个线程都会进入 if 语句块内。虽然在 if 语句块内有加锁操作,但是两个线程都会执行 uniqueInstance = new Singleton(); 这条语句,只是先后的问题,那么就会进行两次实例化。因此必须使用双重校验锁,也就是需要使用两个 if 语句。

if (uniqueInstance == null) {synchronized (Singleton.class) {uniqueInstance = new ChocolateBoiler();}
}

uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行:

  1. 为 uniqueInstance 分配内存空间
  2. 初始化 uniqueInstance
  3. 将 uniqueInstance 指向分配的内存地址

但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1>3>2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。

使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。

4.5 静态内部类实现

当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 getUniqueInstance() 方法从而触发 SingletonHolder.INSTANCE 时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次。

这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。

public class ChocolateBoiler {private boolean empty;private boolean boiled;private static class ChocolateBoilerHolder{private static ChocolateBoiler uniqueInstance=new ChocolateBoiler();}private ChocolateBoiler() {empty = true;boiled = false;}public static ChocolateBoiler getInstance() {return ChocolateBoilerHolder.uniqueInstance;}public void fill() {if (isEmpty()) {empty = false;boiled = false;// fill the boiler with a milk/chocolate mixture}}public void drain() {if (!isEmpty() && isBoiled()) {// drain the boiled milk and chocolateempty = true;}}public void boil() {if (!isEmpty() && !isBoiled()) {// bring the contents to a boilboiled = true;}}public boolean isEmpty() {return empty;}public boolean isBoiled() {return boiled;}
}

4.6 枚举实现

public enum Singleton {INSTANCE;private String objName;public String getObjName() {return objName;}public void setObjName(String objName) {this.objName = objName;}public static void main(String[] args) {// 单例测试Singleton firstSingleton = Singleton.INSTANCE;firstSingleton.setObjName("firstName");System.out.println(firstSingleton.getObjName());Singleton secondSingleton = Singleton.INSTANCE;secondSingleton.setObjName("secondName");System.out.println(firstSingleton.getObjName());System.out.println(secondSingleton.getObjName());// 反射获取实例测试try {Singleton[] enumConstants = Singleton.class.getEnumConstants();for (Singleton enumConstant : enumConstants) {System.out.println(enumConstant.getObjName());}} catch (Exception e) {e.printStackTrace();}}
}

该实现在多次序列化再进行反序列化之后,不会得到多个实例。而其它实现需要使用 transient 修饰所有字段,并且实现序列化和反序列化的方法。

该实现可以防止反射攻击。在其它实现中,通过 setAccessible() 方法可以将私有构造函数的访问级别设置为 public,然后调用构造函数从而实例化对象,如果要防止这种攻击,需要在构造函数中添加防止多次实例化的代码。该实现是由 JVM 保证只会实例化一次,因此不会出现上述的反射攻击。

5.Examples

  • Logger Classes
  • Configuration Classes
  • Accesing resources in shared mode
  • Factories implemented as Singletons

6. JDK

  • java.lang.Runtime#getRuntime()
  • java.awt.Desktop#getDesktop()
  • java.lang.System#getSecurityManager()

Java设计模式(五):单例设计模式相关推荐

  1. 笔记:Java中的单例设计模式

    之前接触过单例模式,当初不明白这样的设计用意,今天特地研究了下java中的单例设计模式的用处及用法. 单例模式:单例模式是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例类的特殊类.一个类 ...

  2. java单例设计模式懒汉_Java设计模式之单例设计模式(懒汉、饿汉)

    [toc] Java设计模式之单例设计模式(懒汉.饿汉) 相信面试过的初中级Java开发的朋友可能都有遇到过单例设计模式的笔试题吧,如果之前没有背下来或者不理解,可以看看下面这篇文章,应该足够应付笔试 ...

  3. java软件设计模式只单例设计模式

    概述 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性. 毫无疑问,设计 ...

  4. Java中的单例设计模式

    什么是单例设计模式 所谓单例设计模式,就是采取一定的方法保证整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法. 目的:使用着在main方法中就不可以自己创建实例对象 ...

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

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

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

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

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

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

  8. Java面试题:单例设计模式、适配器模式的不同方式

    QUESTION:单例设计模式.适配器模式的不同方式? ANSWER: 1.单例设计模式,适配器设计模式     单利设计模式:             在java中,单例模式是指为了保证类在内存中只 ...

  9. JAVA设计模式之单例设计模式

    单例模式,是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例的特殊类.通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例.即一个类只有一个对象实例. 在JAVA中实现单例,必须了 ...

  10. 26、Java 简单实现单例设计模式(饿汉式和懒汉式)

    文章目录 一.概念 二.饿汉式 三.懒汉式 一.概念 ✏️[Singleton Pattern]如果一个类被设计成单例设计模式,则在整个应用程序运行过程中,该类只能存在一个实例. 二.饿汉式 思考:如 ...

最新文章

  1. crontab 总结
  2. 轰动程序员圈的大事:女程序员将代码写到退休,返聘再续传奇
  3. Python全栈工程师(Python3 所有基础内容 0-0)
  4. 企业级 SpringBoot 教程 (十七)上传文件
  5. spark 1.6.0 简单使用
  6. 不会玩电脑怎么学计算机,不会玩电脑怎么学
  7. oracle 11g b表空间什么情况下自动增加,Oracle 11g表空间——创建和扩展(永久)表空间...
  8. [css] pseudo-class与pseudo-element有什么区别?
  9. js for in 遍历对象与数组
  10. java函数调用约定_2020-09-04:函数调用约定了解么?
  11. Python2.x 和 3.x 的区别
  12. (8)Linux内核中的hash与bucket
  13. 调试错误:ValueError: Protocol message Feature has no quot;featurequot; field.
  14. (原创)日志处理(修改)
  15. matlab 2017a安装教程
  16. OPNsense用户手册-基于虚拟机和云的安装
  17. 在Azure的云服务器上搭建个人网站
  18. 华为S5700交换机链路聚合配置
  19. 京东拟申请在北京南六环试点:用无人机送快递
  20. 获取微信昵称乱码php,Android 微信登录昵称乱码问题,及获取微信用户信息

热门文章

  1. 计算机网络总结之计算机概述
  2. Android攻城狮ListView
  3. BZOJ 3870: Our happy ending( 状压dp )
  4. Codeforces Gym 100342J Problem J. Triatrip 求三元环的数量 bitset
  5. linux下的socket通信小程序分享——第三圣子
  6. phpMyAdmin import.php 跨站脚本漏洞
  7. 关于64位WIN7下正确建立JAVA开发环境(转
  8. leetcode 102.二叉树的层序遍历
  9. 嵌入式Linux操作系统学习规划,学习嵌入式开发需要哪些知识?
  10. 2019年——欢度中秋,喜迎国庆