设计模式系列:单例模式

news2024/11/27 4:14:04

作者持续关注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.coloradmin.cn/o/1579845.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CentOS7.9创建本地yum源操作步骤报错解决方法

1.基础信息 CentOS7.9-mini最小化安装的系统&#xff0c;在离线安装rpm时候需要大量依赖&#xff0c;需要花费大量时间去查找依赖包。受于环境限制无法接入互联网使用公开yum源&#xff0c;于是便有了搭建本机yum源的想法&#xff0c;在网上下载CentOS7.9标准版“CentOS-7-x86_…

代码随想录阅读笔记-回溯【组合】

题目 给定两个整数 n 和 k&#xff0c;返回 1 ... n 中所有可能的 k 个数的组合。 示例: 输入: n 4, k 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ] 思路 本题是回溯法的经典题目。 直接的解法当然是使用for循环&#xff0c;例如示例中k为2&#xff0c;很容…

查分约束学习

问题模型&#xff1a; 有n个变量&#xff1a;&#xff0c;有m个约束条件 令差分数组&#xff0c;可以知道如果x1x2<q&#xff0c;那么与j和i-1有关联 由画图可知&#xff0c;如果有在i-1至j建立的有向图中跑最短路&#xff0c;那么dis[n]即为最小的约束变量 另外&#x…

javaWeb车辆管理系统设计与实现

摘 要 随着经济的日益增长,车辆作为最重要的交通工具,在企事业单位中得以普及,单位的车辆数目已经远远不止简单的几辆,与此同时就产生了车辆资源的合理分配使用问题。 企业车辆管理系统运用现代化的计算机管理手段&#xff0c;不但可以对车辆的使用进行合理的管理&#xff0c;…

SpringBoot 定时任务实践、定时任务按指定时间执行

Q1. springboot怎样创建定时任务&#xff1f; 很显然&#xff0c;人人都知道&#xff0c;Scheduled(cron ".....") Q2. 如上所示创建了定时任务却未能执行是为什么&#xff1f; 如果你的cron确定没写错的话 cron表达式是否合法&#xff0c;可参考此处&#xff0c…

刷题之Leetcode59题(超级详细)

59.螺旋矩阵II 力扣题目链接(opens new window)https://leetcode.cn/problems/spiral-matrix-ii/ 给定一个正整数 n&#xff0c;生成一个包含 1 到 n^2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的正方形矩阵。 示例: 输入: 3 输出: [ [ 1, 2, 3 ], [ 8, 9, 4 ], [ …

云原生:5分钟了解一下Kubernetes是什么

在当今的云计算时代&#xff0c;容器化技术变得越来越重要。它能够帮助开发者更高效地部署和管理应用程序。而Kubernetes&#xff0c;作为容器编排领域的领军者&#xff0c;正逐渐成为企业构建和管理云原生应用的核心工具。 近期将持续为大家分享Kubernetes相关知识&#xff…

蓝桥杯 历届真题 双向排序【第十二届】【省赛】【C组】

资源限制 内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 改了半天只有60分&#xff0c;还是超时&#xff0c;还不知道怎么写&#xff0c;后面再看吧┭┮﹏┭┮ #include<bits/stdc.h> …

Python:如何对FY3D TSHS的数据集进行重投影并输出为TIFF文件以及批量镶嵌插值?

完整代码见 Github&#xff1a;https://github.com/ChaoQiezi/read_fy3d_tshs&#xff0c;由于代码中注释较为详细&#xff0c;因此博客中部分操作一笔带过。 01 FY3D的HDF转TIFF 1.1 数据集说明 FY3D TSHS数据集是二级产品(TSHS即MWTS/MWHS 融合大气温湿度廓线/稳定度指数/…

Facebook广告投放数据API对接流程

说明&#xff1a;仅供学习使用&#xff0c;请勿用于非法用途&#xff0c;若有侵权&#xff0c;请联系博主删除 作者&#xff1a;zhu6201976 一、需求背景 App在Facebook、Google等巨头进行广告投放&#xff0c;想要拿到实时广告投放效果数据&#xff0c;如曝光、点击、花费、触…

最少按键次数

题目描述 给你一个字符串 s&#xff0c;由小写英文字母组成。 电话键盘上的按键与 不同 小写英文字母集合相映射&#xff0c;可以通过按压按键来组成单词。例如&#xff0c;按键 2 对应 ["a","b","c"]&#xff0c;我们需要按一次键来输入 &quo…

C语言从入门到实战————文件操作

目录 前言 1. 为什么使用文件&#xff1f; 2. 什么是文件&#xff1f; 2.1 程序文件 2.2 数据文件 2.3 文件名 3. ⼆进制文件和文本文件&#xff1f; 4. 文件的打开和关闭 4.1 流和标准流 4.1.1 流 4.1.2 标准流 4.2 文件指针 4.3 文件的打开和关闭 5. 文…

公寓水电表预付费控制系统

随着现代化管理理念的不断发展&#xff0c;许多公寓管理者开始寻求更加高效、透明的水电计费方式。在此趋势下&#xff0c;公寓水电表预付费控制系统应运而生&#xff0c;成为了一种创新的能源管理解决方案。它通过预付费方式&#xff0c;帮助住户和管理者实现了更加精确和便捷…

Jmeter针对多种响应断言的判断

有时候response返回的结果并非一种&#xff0c;有多种&#xff0c;需要对这几种进行判断的时候需要使用Bean Shell。 &#xff08;1&#xff09;首先获取响应数据 String response prev.getResponseDataAsString(); ResponseCode 响应状态码 responseHeaders 响应头信息 res…

Cyber Weekly #1

赛博新闻 1、弱智吧竟成最佳中文AI训练数据&#xff1f;&#xff01;中科院等&#xff1a;8项测试第一&#xff0c;远超知乎豆瓣小红书 使用弱智吧数据训练的大模型&#xff0c;跑分超过百科、知乎、豆瓣、小红书等平台&#xff0c;甚至是研究团队精心挑选的数据集。弱智吧数…

第十三章 OpenGL ES-RGB、HSV、HSL模型介绍

第十三章 OpenGL ES-RGB、HSV、HSL模型详细介绍 第一章 OpenGL ES 基础-屏幕、纹理、顶点坐标 第二章 OpenGL ES 基础-GLSL语法简单总结 第三章 OpenGL ES 基础-GLSL渲染纹理 第四章 OpenGL ES 基础-位移、缩放、旋转原理 第五章 OpenGL ES 基础-透视投影矩阵与正交投影矩阵…

vue+springboot实现JWT登录验证

目录 前言概念实际演示路由信息初始访问登录界面登录验证验证过期 vue实现依赖引入main.js获取和设置token工具类登录方法实体登录方法axios请求 router配置 springboot实现依赖引入JWT工具类忽视jwt验证注解拦截器逻辑跨域&调用拦截器配置登录接口&验证token接口 结语…

算法打卡day36|动态规划篇04| 01背包理论基础、416. 分割等和子集

目录 01背包理论基础 01背包问题描述 01背包解法 二维数组 一维数组 算法题 Leetcode 416. 分割等和子集 个人思路 解法 动态规划 01背包理论基础 不同的背包种类&#xff0c;虽然有那么多中南背包&#xff0c;但其中01背包和完全背包是重中之重&#xff1b; 01背包问…

flutter多入口点entrypoint

native中引擎对象本身消耗内存(每个引擎对象约莫消耗42MB内存) 多引擎&#xff1a;native多引擎>启动>flutter多入口点entrypoint>多main函数>多子包元素集>多(子)程序 单引擎(复用)&#xff1a;native单引擎>复用启动>flutter多入口点entrypoint>多m…

六、企业级架构缓存篇之memcached

一、memcached概述 1、网站架构优化流程&#xff1a; LNMP架构中网站应用访问流程&#xff1a; 浏览器 (app) → web 服务器 → 后端服务 (php) → 数据库 (mysql) 访问流程越多&#xff0c;访问速度越慢&#xff0c;出现问题的几率也越大。 网站访问流程优化思路&#xff1…