【设计模式-1】图文并茂:单例模式的使用场景及7种代码实现

 接上一篇:【设计原则】程序设计7大原则

1.什么是单例模式

 在了解单例模式前,我们先来看一下它的定义:

确保一个类只有一个实例,而且自行实例化并且自行向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法,单例模式是一种对象的创建型模式。

 可以看到在定义中,提到了3个要素:

  1. 某一个类(单例类)只能有一实例;
  2. 单例类必须自行创建这个实例;
  3. 单例类必须向整个系统提供这个唯一的实例。

 OK,有了这三点,其实就把创建单例模式的步骤罗列出来了,我们先看一下单例模式的类图,有个模糊的概念。
在这里插入图片描述
 类图还是比较简单清晰的,自关联关系,下面我们来看下为什么要用单例模式呢?

2. 为什么用单例模式

 单例模式其实是很简单的一种模式,代码也很好实现,但是我在学习单例模式的时候,一直对它怎么使用比较模糊,这里涉及到两个疑问点,一是为什么要用单例模式,二是需在哪些场景下用单例模式呢?
 要搞清楚这两个问题,我们先从单例模式最常用的一个场景说起,就是线程池工具类,相信很多人都用过。我们看一下线程池的使用场景,在之前不使用线程池的时候,程序每次要执行一个现成任务,就会new一个新的线程,然后执行任务,再销毁线程。这个过程中,线程的创建和销毁对系统资源的开销是巨大的,如果使用线程池呢,在这个池中,始终维护一部分存活的线程,循环执行我们的任务,达到减少资源开销的目的。

ThreadPoolExecutor INSTANCE = new ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);

 在工具类中,我们会通过 ThreadPoolExecutor这个执行器创建线程池,在一个系统中,应该存在1个或者少量的线程池,多个任务复用池中的线程就可以。假设就以1个例,要复用这1个池中的多个线程,线程池必须有1份,否则每次调用工具类,都会new ThreadPoolExecutor创建1个线程池,也会伴随多个线程的创建和销毁动作,在用完里面的线程后就再也不用了,那线程池就没存在的意义了,既占用了系统的内存资源,又不符合业务场景,所以这里使用单例模式来维护唯一的线程池就很有必要了。
 看到这里,为什么要使用单例模式就比较清晰了,单个对象复用可以减少系统资源消耗,对于一些需要频繁创建和销毁的对象,使用单例模式无疑可以提高系统的整体性能。
 站在应用场景来看,一个类能不能做成单例,最容易区分的地方就在于,如果存在两个或两以上的实例会造成错误或业务场景上的歧义,也就是这个类在整个应用中,某一个时刻应该只有一个状态体现。那么除了刚才线程池工具类的例子,还有那些实际的应用场景呢?比如:操作系统中的“任务管理器”,“回收站”,网站的“计时器”,或者自定义数据库表的“自增主键计数器”等等,而且spring中的bean默认也是单例的。

3.单例模式的7种代码实现

 单例模式的代码实现有很多种,相信大家也都听过懒汉式与饿汉式,以及饿汉式下面的线程安全问题,其实真正开发中只要熟悉一到两种就可以,但是面试时经常被问到各种写法,接着就来看下这几种代码实现的写法吧。

3.1 饿汉式

  • 优点:类初始化时就创建此唯一的实例,不存在并发问题,能保证实例的唯一性。
  • 缺点:没有起到延迟加载的效果,如果此实例在整个应用声明周期中都不使用,会造成系统资源浪费。
  • 代码:
public class Singleton {// 1.构造方法私有化private Singleton() {}// 2.自行创建这个实例private static Singleton instance = new Singleton();// 3.提供外部可以访问的此实例的方法public static Singleton getInstance() {return instance;}
}

3.2 懒汉式-原始版本

  • 优点:效率高,延迟加载
  • 缺点:存在线程安全问题,并发场景下可能会创建多份实例,只能在单线程场景下使用
  • 代码:
public class Singleton {// 1.构造方法私有化private Singleton() { }// 2.自行创建这个实例private static Singleton instance = null;// 3.自行提供外部访问此实例的方法public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

 在多线程场景下,如果有两个现成同时执行到了 instance == null 且都成立,会重复创建实例。

3.3 懒汉式-线程安全版本

  • 优点:可以保证线程安全和实例的唯一性。
  • 缺点:锁住了整个获取实例的方法,效率较低。
  • 代码:
public class Singleton {// 1.构造方法私有化private Singleton() { }// 2.自行创建这个实例private static Singleton instance = null;// 3.自行提供全局访问此实例的方法public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

3.4 懒汉式-效率提升版本

  • 优点:锁范围变小,延迟加载
  • 缺点:仍然存在现成不安全的问题
  • 代码:
public class Singleton {// 1.构造方法私有化private Singleton() { }// 2.自行创建这个实例,注意用volatile修饰private static Singleton instance = null;// 3.自行给全局提供访问此实例的方法public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {instance = new Singleton();}}return instance;}
}

 这里虽然锁住了实例创建的代码片段,但是如果存在多个线程都进入到if (instance == null) {}判断里面,且等待锁释放的状态,也会造成创建多个实例的问题,是线程不安全的。

3.5 懒汉式 - 双重判断版

  • 优点:延迟加载,线程安全,锁定范围小
  • 缺点:代码略显复杂,可读性略低,其实可以忽略
  • 代码:
public class Singleton {// 1.构造方法私有化private Singleton() { }// 2.自行创建这个实例private static volatile Singleton instance = null;// 3.自行给全局提供访问此实例的方法public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}

 由于存在双重判断,即便后来等待的线程持有锁之后,也会再次判断此实例是否已创建,但是要需要注意用volatile修饰,volatile 的作用主要是禁止指定重排序。假设在不使用 volatile 的情况下,两个线程A、B,都是第一次调用该单例方法,线程A先执行 singleton = new Singleton(),但由于构造方法不是一个原子操作,编译后会生成多条字节码指令,由于 JAVA的 指令重排序,可能会先执行 singleton 的赋值操作,该操作实际只是在内存中开辟一片存储对象的区域后直接返回内存的引用,之后 singleton 便不为空了,但是实际的初始化操作却还没有执行。如果此时线程B进入,就会拿到一个不为空的但是没有完成初始化的singleton 对象,所以需要加入volatile关键字,禁止指令重排序优化,从而安全的实现单例。

3.6 静态内部类方式

  • 优点:线程安全,利用静态内部类的初始化创建实例,实现延迟加载,效率高。
  • 代码:
public class Singleton {// 1.构造方法私有化private Singleton() { }// 2.自行创建这个唯一的实力private static class SingletonInstance {private static final Singleton INSTANCE = new Singleton();}// 3.自行给全局提供访问这个实例的方法public static Singleton getInstance() {return SingletonInstance.INSTANCE;}
}

3.7 枚举实现

  • 代码:
public enum SingletonEnum {INSTANCE;public String method() {return "what you need!";}
}

 枚举类的实现方式可以说是单例模式的最佳实践,在《Effective Java》这本书中,作者就提到“单元素的枚举类型已经成为实现Singleton的最佳方法”。
 枚举类的实现方式不仅可以解决上面所述的所有问题,还可以防止通过反射和反序列化来重复创建新的实例,Java虚拟机天然可以保证枚举对象的唯一性,在很多优秀的框架中,经常可以看到通过枚举实现的单例模式。

4.总结

 单例模式作为一种目标明确、结构简单、理解容易的设计模式,在开发工作中使用的频率相当的高,写在最后,简单总结一下单例模式的优缺点。

4.1 优点

  1. 单例模式提供了唯一实例的访问权限,可以限制客户端如何它;
  2. 对象的唯一性可以减少频繁创建和销毁对象过程,能够节省系统资源;
  3. 基于单例模式,可以扩展出指定个数的多例对象,即多例类,灵活性也很高。

4.2 缺点

  1. 单例模式没有抽象层,只有实现层,因此扩展困难;
  2. 在一定程度上为了单一职责,因为单例模式既提供了对象的创建职责,又提供了业务方法,导致创建过程和业务功能耦合在一块。
  3. 部分垃圾回收机制会回收长时间不用的对象,这将导致单例对象有被销毁的风险,下次使用重新被实例化,违背了单例模式的初衷(此条不太理解,有待验证,摘抄自《设计模式艺术》)。

 下一遍链接:【设计模式-2】简单工厂模式的代码实现及使用场景

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/311289.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Git:常用命令(二)

查看提交历史 1 git log 撤消操作 任何时候&#xff0c;你都有可能需要撤消刚才所做的某些操作。接下来&#xff0c;我们会介绍一些基本的撤消操作相关的命令。请注意&#xff0c;有些操作并不总是可以撤消的&#xff0c;所以请务必谨慎小心&#xff0c;一旦失误&#xff0c…

详解Med-PaLM 2,基于PaLM 2的专家级医疗问答大语言模型

详解Med-PaLM 2&#xff0c;基于PaLM 2的专家级医疗问答大语言模型 - 知乎 目录 摘要&#xff1a; 1 介绍 2 相关工作 3 方法 3.1 数据集 3.2 建模 3.3 多项选择评估 3.4 重叠分析 &#xff08;Overlap analysis &#xff09; 3.5 长形式评估&#xff08;Long-form ev…

4.28 构建onnx结构模型-Unfold

前言 构建onnx方式通常有两种&#xff1a; 1、通过代码转换成onnx结构&#xff0c;比如pytorch —> onnx 2、通过onnx 自定义结点&#xff0c;图&#xff0c;生成onnx结构 本文主要是简单学习和使用两种不同onnx结构&#xff0c; 下面以 Unfold 结点进行分析 方式 方法…

深度学习中Batch/Layer/Instance/Group normalization方法

图片中&#xff0c;N是batch size&#xff0c; c是channel。 BN&#xff1a;在每一个channel内&#xff0c;对H&#xff0c;W&#xff0c;Batch做平均LN&#xff1a;在每一个batch内&#xff0c;对H&#xff0c;W&#xff0c;Channel做平均IN&#xff1a;在每一个channel和bat…

【基础】【Python网络爬虫】【10.验证码处理】OCR识别,Tesseract ,ddddocn识别,打码平台,滑块验证码(附大量案例代码)(建议收藏)

Python网络爬虫基础 验证码处理一. OCR识别1. Tesseract 引擎的安装windows引擎环境安装Mac系统引擎环境安装安装 tesseract查看 tesseract 版本安装过程遇到的报错解决方法下载中文包中文包存放目录查看全部语言库python 安装 pytesseract 和 pillow识别图片中文字体 Linux系统…

【并行计算】GPU,CUDA

一、CUDA层次结构 1.kernel核函数 一个CUDA程序是一个kernel核函数被GPU的多个计算单元并行执行的过程&#xff0c;CUDA给了如下抽象 dim3 threadsPerBlock(4, 3, 1); dim3 numBlocks(3, 2, 1); matrixAdd<<<numBlocks, threadsPerBlock>>>(A, B, C); 2.G…

Huggingface 超详细介绍

10年 软件研发及数据处理经验&#xff0c;关注机器学习&#xff0c;机器人技术&#xff0c;自动驾驶&#xff0c;医疗信息学等方面 Hugging face 起初是一家总部位于纽约的聊天机器人初创服务商&#xff0c;他们本来打算创业做聊天机器人&#xff0c;然后在github上开源了一个…

毫秒格式化

## 计算当前毫秒数&#xff1a; const [start,setStart] useState(new Date().getTime())useEffect(()>{setInterval(()>{setCurrMill(new Date().getTime()-start)},1)},[]) ## 格式化毫秒 function formatMilliseconds(milliseconds) {const totalSeconds Math.flo…

新建虚拟环境并与Jupyter内核连接

第一步:在cmd里新建虚拟环境,shap38是新建的虚拟环境的名字 ,python=3.x conda create -n shap38 python=3.8第二步,安装ipykernel,打开anconda powershell prompt: 虚拟环境的文件夹位置,我的如图所示: 进入文件夹并复制地址: 输入复制的文件夹地址更改文件夹:…

学习体系结构 - AArch64内存管理

学习体系结构 - AArch64内存管理 Learn the architecture - AArch64 memory management Version 1.2 个人的英语很一般&#xff0c;对拿不准的翻译校准在后面添加了英文原文。 1、 概述 本指南介绍了AArch64中的内存转换&#xff0c;这是内存管理的关键。它解释了如何将虚拟地…

Redis内存使用率高,内存不足问题排查和解决

问题现象 表面现象是系统登录突然失效&#xff0c;排查原因发现&#xff0c;使用redis查询用户信息异常&#xff0c;从而定位到redis问题 if (PassWord.equals(dbPassWord)) {map.put("rtn", 1);map.put("value", validUser);session.setAttribute("…

决策规划框架 - (解耦:路径规划和速度规划 | 耦合:行为规划和轨迹规划)

1 解耦策略 1.1 概述 核心思想&#xff1a; &#xff08;1&#xff09;路径规划&#xff1a;假定环境是“静态的”&#xff0c;将障碍物投影到参考路径上&#xff0c;并规划路径避开这些障碍物 &#xff08;2&#xff09;速度规划&#xff1a;根据路径规划给出的路径&#xf…