设计模式系列:单例模式

news/2025/2/21 20:17:56/

 作者持续关注WPS二次开发专题系列,持续为大家带来更多有价值的WPS开发技术细节,如果能够帮助到您,请帮忙来个一键三连,更多问题请联系我(WPS二次开发QQ群:250325397),摸鱼吹牛嗨起来!

定义

单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。

特点

  1. 单例类只有一个实例对象;
  2. 该单例对象必须由单例类自行创建;
  3. 单例类对外提供一个访问该单例的全局访问点。

使用场景

单例模式可以保证在一个 JVM 中只存在单一实例。单例模式的应用场景主要有以下几个方面。

  • 需要频繁创建的一些类,使用单例可以降低系统的内存压力,减少 GC。
  • 某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
  • 某些类创建实例时占用资源较多,或实例化耗时较长,且经常使用。
  • 某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
  • 频繁访问数据库或文件的对象。
  • 对于一些控制硬件级别的操作,或者从系统上来讲应当是单一控制逻辑的操作,如果有多个实例,则系统会完全乱套。
  • 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。

模式结构

单例模式的主要角色如下。

  • 单例类:包含一个实例且能自行创建这个实例的类。
  • 访问类:使用单例的类。

具体实现

(1) 饿汉式--(线程安全,实用)

/*** 单例模式--单例模式的饿汉式(线程安全,可用)* <pre>* (1)私有化该类的构造函数* (2)通过new在本类中创建一个本类对象* (3)定义一个公有的方法,将在该类中所创建的对象返回* 优点:从它的实现中我们可以看到,这种方式的实现比较简单,在类加载的时候就完成了实例化,避免了线程的同步问题。* 缺点:由于在类加载的时候就实例化了,所以没有达到Lazy Loading(懒加载)的效果,也就是说可能我没有用到这个实例,但是它* 也会加载,会造成内存的浪费(但是这个浪费可以忽略,所以这种方式也是推荐使用的)。* <pre>*/
public class SingletonEHan {private static final SingletonEHan instance = new SingletonEHan();private SingletonEHan() {}private static SingletonEHan getInstance() {return instance;}
}

(2) 懒汉式--(线程安全,可用,效率稍低)

/*** 单例模式--懒汉式线程安全的:(线程安全,效率低,不推荐使用)* <pre>* 缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。* 而其实这个方法只执行一次实例化代码就够了,* 后面的想获得该类实例,直接return就行了。方法进行同步效率太低要改进。* </pre>*/
public class SingletonLanHan {private static SingletonLanHan instance = null;private SingletonLanHan() {}public static synchronized SingletonLanHan getInstance() {if (instance == null) {instance = new SingletonLanHan();}return instance;}
}

(3) 懒汉式--双重校验锁(线程安全, 推荐)

/*** 单例模式--单例模式懒汉式双重校验锁(线程安全, 推荐)* <pre>* 懒汉式变种,属于懒汉式的最好写法,保证了:延迟加载和线程安全* </pre>*/
public class SingletonDoubleCheck {private static volatile SingletonDoubleCheck instance = null;   //关键点0:声明单例对象是静态的private SingletonDoubleCheck() {                            //关键点1:构造函数是私有的}public static SingletonDoubleCheck getInstance() {          //通过静态方法来构造对象if (instance == null) {                                 //关键点2:判断单例对象是否已经被构造synchronized (SingletonDoubleCheck.class) {         //关键点3:加线程锁if (instance == null) {                         //关键点4:二次判断单例是否已经被构造instance = new SingletonDoubleCheck();}}}return instance;}
}
注:instance加了volatile关键字来修饰,既然synchronized已经起到了多线程下原子性、有序性、可见性的作用,为什么还要加 volatile呢,主要原因如下:
a. 防止指令重排序
具体可见: 单例模式与双重检测 - 设计模式 - Java - ITeye论坛

疑问:为什么instance要加volatile关键字来修饰?

解答:

instance = new SingletonDoubleCheck();分三步执行

①给 instance 分配内存

②调用 Singleton 的构造函数来初始化成员变量

③将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)。

但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 ①-②-③ 也可能是 ①-③-②。如果是后者,则在 ③ 执行完毕、② 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。

由于 JVM 具有指令重排的特性,有可能执行顺序变为了 >③>②,具体如下:

public class SingletonDoubleCheck {private static SingletonDoubleCheck instance = null;private SingletonDoubleCheck() {}public static SingletonDoubleCheck getInstance() {if (instance == null) {    // B线程检测到instance不为空synchronized (SingletonDoubleCheck.class) {if (instance == null) {instance = new SingletonDoubleCheck();    // A线程被指令重排了,刚好先赋值了;但还没执行完构造函数。}}}return instance;    // 后面B线程执行时将引发:对象尚未初始化错误。}
}

使用 volatile 的主要原因是其另一个特性:禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。比如上面的例子,取操作必须在执行完 ①-②-③ 之后或者 ①-③-② 之后,不存在执行到 ①-③ 然后取到值的情况。

(4) 静态内部类--(线程安全,推荐)

/*** 单例模式--内部类(线程安全,推荐)* <pre>* 这种方式跟饿汉式方式采用的机制类似,但又有不同。* 两者都是采用了类装载的机制来保证初始化实例时只有一个线程。* 不同的地方:* 在饿汉式方式是只要Singleton类被装载就会实例化,* 内部类是在需要实例化时,调用getInstance方法,才会装载SingletonHolder类* 优点:避免了线程不安全,延迟加载,效率高。* <pre>*/
public class SingletonLazy {private SingletonLazy() {}private static class SingletonHolder {private static final SingletonLazy INSTANCE = new SingletonLazy();}public static SingletonLazy getInstance() {return SingletonHolder.INSTANCE;}
}

(5) 枚举--(线程安全,推荐)

/*** 单例模式--枚举(线程安全,可用)* <pre>* 这里SingletonEnum.instance* 这里的instance即为SingletonEnum类型的引用所以得到它就可以调用枚举中的方法了。* 借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象* </pre>*/
public enum SingletonEnum {INSTANCE;public static void main(String[] args) {SingletonEnum obj = SingletonEnum.INSTANCE;System.out.println(obj);}
}

http://www.ppmy.cn/news/1418094.html

相关文章

「Python」数据分析师需要掌握到什么程度?4条告诉你

前言 最近经常收到小伙伴们的留言&#xff1a;做数据分析要精通Python吗&#xff1f; 今天们就来好好盘一盘这个话题。 0基础想入门的小伙伴&#xff0c;如果你决定学习数据分析&#xff0c;却没有编程经验&#xff0c;那么这篇内容会非常适合你&#xff0c;让你的困惑得以解…

Electron+React 搭建桌面应用

创建应用程序 创建 Electron 应用 使用 Webpack 创建新的 Electron 应用程序&#xff1a; npm init electron-applatest my-new-app -- --templatewebpack 启动应用 npm start 设置 Webpack 配置 添加依赖包&#xff0c;确保可以正确使用 JSX 和其他 React 功能&#xff…

二:什么是RocketMQ

RocketMQ是阿里开源的消息中间件产品&#xff0c;纯Java开发&#xff0c;具有高吞吐量、高可用性、适合大规模分布式系统应用的特点,性能强劲(零拷贝技术)&#xff0c;支持海量堆积,在阿里内部进行大规模使用&#xff0c;适合在互联网与高并发系统中应用。 官方文档&#xff1a…

解决 windows+Ubuntu 时间不同步问题

本文所使用的 Ubuntu 系统版本是 Ubuntu 22.04 ! 如果你的电脑装了 Windows Ubuntu 系统&#xff0c;肯定会遇到时间不同步的问题。那么如何解决呢&#xff1f;参考步骤如下&#xff1a; # 步骤一&#xff1a;进入到 Ubuntu 系统# 步骤二&#xff1a;执行如下三条命令即可 sud…

人工智能研究生前置知识—科学计算库numpy

人工智能研究生前置知识—科学计算库numpy numpy是python中做科学计算的基础库&#xff0c;对数组进行操作 整个numpy的操作和使用比较简单因此可以通过案例的学习掌握基本的用法在之后的学习中不断的进行熟悉和补充 创建数组&#xff08;矩阵 &#xff09; 创建的ndarray数组…

【御控物联】 1、物联网介绍

文章目录 一、定义二、起源三、发展四、应用五、未来六、技术资料 一、定义 如何理解物联网&#xff0c;我们引用百度百科的一句话“把所有物品通过信息传感设备与互联网连接起来&#xff0c;进行信息交换&#xff0c;即物物相息&#xff0c;以实现智能化识别和管理”&#xf…

cmake基础教程(12)函数和宏用法

参考: https://cmake.org/cmake/help/latest/command/function.html https://cmake.org/cmake/help/latest/command/macro.html#command:macro 文章目录 函数宏在CMake中,宏(macro)和函数(function)命令用于封装重复的任务,这些任务可能分散在你的CMakeLists文件中。一…

think-queue消息队列的使用方法

1、创建配置文件&#xff1a;config/queue.php <?php return [default > redis,connections > [sync > [type > sync,],database > [type > database,queue > default,table > jobs,connection > null,],redis > [type > redis,queue &g…