设计模式系列:单例模式

 作者持续关注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);
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

RabbitMQ如何保证消息的幂等性???

在RabbitMQ中&#xff0c;保证消费者的幂等性主要依赖于业务设计和实现&#xff0c;而非RabbitMQ本身提供的一种直接功能。 在基于Spring Boot整合RabbitMQ的场景下&#xff0c;要保证消费者的幂等性&#xff0c;通常需要结合业务逻辑设计以及额外的技术手段来实现。以下是一个…

嵌入式学习51-单片机4

知识零碎&#xff1a; nop空指令 CRC校验 为了保证51单片与温度传感18b20 之间的高电平 采用一个上拉电阻改变电平的高低 温度寄存器原理

react17+antd4 动态渲染导航菜单中的icon

在路由信息对照表中的icon可以有两种形式&#xff1a;一种是组件形式&#xff0c;一种是字符串形式的。 在antd4的Menu.Item和SubMenu中的icon属性的格式为&#xff1a; 1.组件形式 这种方法在渲染时很方便&#xff0c;与antd中的Menu.Item中的icon属性的形式是一致的&#…

51单片机+TN901非接触式红外测温设计论文与源码PCB等资料

1、摘要 温度测量技术应用十分广泛&#xff0c;而且在现代设备故障检测领域中也是一项非常重要的技术。但在某些应用领域中&#xff0c;要求测量温度用的传感器不能与被测物体相接触&#xff0c;这就需要一种非接触的测温方式来满足上述测温需求。本论文正是应上述实际需求而设…

如何让MacOS「终端」走代理

在 MacOS 操作系统中&#xff0c;默认情况下&#xff0c;终端命令行不会通过代理进行网络连接。这导致在应用软件研发过程中&#xff0c;许多需要通过命令行下载安装的软件或依赖包无法成功安装。经常出现Failed to connect to xxx port 443 after 75329 ms: Couldnt connect t…

【资源分享】MAC上最好用的截图软件-Snipaste

::: block-1 “时问桫椤“是一个关注本科生到研究生教育阶段的不严肃的公众号&#xff0c;希望能在大家迷茫、难受、困难之时帮助到大家。用广大研究生的经验总结&#xff0c;让大家能尽早的适应研究生生活&#xff0c;尽快的看透科研本质。祝好&#xff01;&#xff01;&#…

OLAP在线实时 数据分析平台

随着业务的增长&#xff0c;精细化运营的提出&#xff0c;产品对数据部门提出了更高的要求&#xff0c;包括需要对实时数据进行查询分析&#xff0c;快速调整运营策略&#xff1b;对小部分人群做 AB 实验&#xff0c;验证新功能的有效性&#xff1b;减少数据查询时间&#xff0…

IntelliJ IDEA 2024.1安装与激活[破解]

一&#xff1a;IDEA官方下载 ①如题&#xff0c;先到IDEA官方下载&#xff0c;简简单单 ②IDEA官方&#xff1a;IntelliJ IDEA – the Leading Java and Kotlin IDE 二&#xff1a;获取脚本 &#x1f31f;网盘下载&#xff1a;jetbra (密码&#xff1a;lzh7) &#x1f31f;获取…

macOS制作C/C++ app

C/C制作macOS .app 一、 .app APP其实是一个文件夹结构&#xff0c;只不过mac的界面中让它看起来像一个单独的文件。 在shell终端或者右键查看包结构即可看到APP的目录结构。 通常的app目录结构如下&#xff1a; _CodeSignature, CodeResources 一般为Mac APP Store上架程序…

Redis的RDB文件

Redis持久化 Redis是操作内存的&#xff0c;所以是内存数据库&#xff0c;断电即失&#xff0c;所以需要持久化 RDB 文件名:dump.rdb # The filename where to dump the DB dbfilename dump.rdb在指定时间间隔内&#xff0c;将内存中的数据&#xff0c;集体快照写入磁盘内&…

FineBI概述

FineBI是一款商业智能&#xff08;Business Intelligence&#xff09;工具&#xff0c;它通过最终业务用户自主分析企业已有的信息化数据&#xff0c;帮助企业发现并解决存在的问题&#xff0c;协助企业及时调整策略做出更好的决策&#xff0c;增强企业的可持续竞争性。它以自助…

HDFS读、写数据流程(图解)以及元数据的checkpoint机制

一、HDFS概述 HDFS集群由NameNode,DataNode,SecondaryNameNode构成。 NameNode&#xff1a;接受用户请求。存储元数据&#xff08;描述文件的数据&#xff0c;如文件名、文件大小、目录名、文件创建时间等&#xff09; DataNode&#xff1a;负责管理用户的文件数据块&#xff0…

openGauss学习笔记-259 openGauss性能调优-使用Plan Hint进行调优-指定不使用全局计划缓存的Hint

文章目录 openGauss学习笔记-259 openGauss性能调优-使用Plan Hint进行调优-指定不使用全局计划缓存的Hint259.1 功能描述259.2 语法格式259.3 示例 openGauss学习笔记-259 openGauss性能调优-使用Plan Hint进行调优-指定不使用全局计划缓存的Hint 259.1 功能描述 全局计划缓…

Linux内核之原子操作atomic_t用法实例(三十九)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

MYSQL 8.0版本修改用户密码(知道登录密码)和Sqlyog错误码2058一案

今天准备使用sqlyog连接一下我Linux上面的mysql数据库&#xff0c;然后就报如下错误 有一个简单的办法就是修改密码为password就完事!然后我就开始查找如何修改密码! 如果是需要解决Sqlyog错误码2058的话&#xff0c;执行以下命令&#xff0c;但是注意root对应host是不是loca…

【目标检测】-入门知识

1、回归与分类问题 回归问题是指给定输入变量(特征)和一个连续的输出变量(标签),建立一个函数来预测输出变量的值。换句话说,回归问题的目标是预测一个连续的输出值,例如预测房价、股票价格、销售额等。回归问题通常使用回归分析技术,例如线性回归、多项式回归、决策树…

【优选算法专栏】专题十八:BFS解决拓扑排序(一)

本专栏内容为&#xff1a;算法学习专栏&#xff0c;分为优选算法专栏&#xff0c;贪心算法专栏&#xff0c;动态规划专栏以及递归&#xff0c;搜索与回溯算法专栏四部分。 通过本专栏的深入学习&#xff0c;你可以了解并掌握算法。 &#x1f493;博主csdn个人主页&#xff1a;小…

【第七篇】使用BurpSuite进行主动、被动扫描和主动、被动爬虫

文章目录 前言主动扫描被动扫描主动爬虫被动爬虫前言 Burp Scanner 既可以用作全自动扫描仪,也可以用作增强手动测试工作流程的强大手段。 扫描网站涉及两个阶段: 抓取内容和功能: Burp Scanner 首先在目标站点周围导航,密切反映真实用户的行为。它对站点的结构和内容以及…

STM32F4XX软件I2C驱动MPU6050

MPU6050 一、简介 MPU6050是一款6轴姿态传感器&#xff0c;可以测量芯片自身X、Y、Z轴的加速度、角速度参数&#xff0c;通过数据融合&#xff0c;可进一步得到姿态角&#xff0c;常应用于平衡车、飞行器等需要检测自身姿态的场景。 3轴加速度计&#xff08;Accelerometer&…

电子档案数据迁移什么意思?电子档案数据迁移流程

电子档案数据迁移是指将现有的电子档案数据从一个系统或存储设备迁移到另一个系统或存储设备的过程。这个过程可以包括将数据从旧的存储介质转移到新的存储介质&#xff0c;或将数据从一个系统迁移到另一个系统。电子档案数据迁移通常是为了更好地管理和保护档案数据&#xff0…