当前位置: 首页 > news >正文

单例模式之饿汉式

       

目录

1 单例模式的程序结构

2 饿汉式单例模式的实现

3 饿汉式线程安全

4 防止反射破坏单例

5 总结


       单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。所谓单例就是在系统中只有一个该类的实例,并且提供一个访问该实例的全局访问方法。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式,关于单例模式的原理,可参考文末的链接文章。

       单例的实现分为三个步骤:

  1. 构造方法私有化。即不能在类外实例化,只能在类内实例化。
  2. 在本类中完成自身的初始化,自己创建本类的实例,且是唯一的实例。
  3. 在本类中提供给外部获取实例的方式,提供访问该实例的全局静态方法getInstance(),来获取该类的唯一实例引用。

       单例模式的特点:从系统启动到终止,整个过程只会产生一个实例。因为单例提供了唯一实例的全局访问方法,所以它可以优化共享资源的访问,避免对象的频繁创建和销毁,从而可以提高性能。单例模式常见的应用场景如下:Windows任务管理器、数据库连接池、Java中的Runtime、Spring中Bean的默认生命周期等。

1 单例模式的程序结构

        单例模式简化后的类图如下所示:

        类图显示了该类的一个私有静态成员变量、一个公有静态方法(又叫静态成员函数)以及一个私有的构造函数。虽然这是简化后的结构,但也是单例模式的主要结构。顺便说一下,这个类图只是个简化后的结构,该类通常还会有其它的非静态的成员变量和方法,当获取到该类的唯一实例后,就在该实例上调用这些其它方法来执行该类提供的功能。

        私有的构造函数保证只能在类内部实例化;静态成员变量用来保存该类的唯一实例,该静态成员变量必须是private的,以防止用户可以直接访问到它。如果用户想要访问该单例类的唯一实例,它只能调用该类的静态方法(getInstance)。

       注意static(静态成员变量、静态方法)的使用。从语法上来说,创建的单例类是不允许被其他程序用new来创建该对象的,所以只能将这个单例类中的方法定义成静态的,而静态方法又不能去访问非static成员的,所以因此类自定义的实例变量也必须是静态的。

       这里不妨回顾一下,静态成员变量是属于整个类的,仅在类的初次加载时初始化,在类被销毁时才会被回收。通过该类实例化的所有对象都共享该静态变量,任一对象对于该静态变量的修改都会影响所有的对象。静态方法同样是属于整个类的,可以通过类名与对象名进行访问,而非静态成员是随着对象的创建而被实例化的。在调用静态方法时,可能对象还没有实例化,自然也就没有对象的非静态成员的实例化,所以无法访问非静态的成员。

2 饿汉式单例模式的实现

       在Java中实现单例模式通常有两种形式.:

  • 饿汉式:类加载时,就进行对象实例化。
  • 懒汉式:第一次引用类时,才进行对象实例化。

      这里主要聚焦于饿汉式。饿汉式代码实现如下:

public class HungrySingLeton {// 创建HungrySingLeton 的一个对象private static final HungrySingLeton instance = new HungrySingLeton();// 让构造函数为private,这样该类就不会被实例化private HungrySingLeton() {}// 获取唯一可用的对象public static HungrySingLeton getInstance(){return instance;}/*** 获取对象的内存地址* @return*/public long getRamAddress() {return VM.current().addressOf(this);}
}

       注意instance变量加了final的,一般建议加final,除非说有释放资源等特殊要求。这种方式简单,也比较常用,在类创建的同时已经创建好一个静态的对象供系统使用,执行效率高。

3 饿汉式线程安全

       饿汉式单例通过getInstance获取的单例,在类加载时已经初始化完毕,在多线程环境下也是安全的,所以不需要同步。我们可以通过测试来验证。

       先写一个公共方法,用于多线程环境下获取单例:

    /*** 公共方法,在多线程环境下获取单例,避免重复编写测试代码* @param threadCount  线程数* @param func  函数,用于获取单例* @param <T>*/public static <T> List<T> getSingLetonObjList(int threadCount, Supplier<T> func) {List<T> list = new ArrayList<>();ExecutorService executorService = Executors.newFixedThreadPool(threadCount);IntStream.range(0, threadCount).forEach(i -> {executorService.submit(() -> {// 同步锁,保证保证内存的可见性,否则在多线程环境下可能出现空对象synchronized (SingletonTest.class) {list.add(func.get());}});});executorService.shutdown();while(true) {// 所有的子线程都结束了if(executorService.isTerminated()) {if(list.size() == threadCount) {return list;}}}}

       这个公共方法可以接收一个函数,该函数就是获取单例的方法。值得注意的是那个同步锁,如果不加的话,在多线程环境下,可能会获得空的单例导致后续调用getRamAddress方法时出现空指针。当然,也可以用Sytem.out.print方法代替:

synchronized (SingletonTest.class) {list.add(func.get());
}等价于:System.out.print("");
list.add(func.get());

       因为print方法本身就自带锁:

public void println(String x) {synchronized (this) {print(x);newLine();}
}

       有了公共方法后,接着写测试代码:

    public static void hungrySingLetonTest() {Supplier func = () ->  HungrySingLeton.getInstance();List<HungrySingLeton> list = getSingLetonObjList(10, func);list.stream().forEach(item -> {System.out.println("内存地址: " + item.getRamAddress());});}

       测试结果如下:

       多线程下获取到的单例始终是同一个对象,他们的内存地址都一样。

4 防止反射破坏单例

       到这里,可能会认为已经很OK了。但是,Java还有个反射机制,通过反射,可以轻易破解单例的安全。

    public static void reflectionTest() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {Class<HungrySingLeton> clazz = HungrySingLeton.class;// 获取HungrySingLeton的默认构造函数Constructor<HungrySingLeton> constructor = clazz.getDeclaredConstructor();constructor.setAccessible(true);// 调用默认构造函数创建实例HungrySingLeton h1 = constructor.newInstance();HungrySingLeton h2 = constructor.newInstance();System.out.println(h1.getRamAddress());System.out.println(h2.getRamAddress());}

       得到的结果如下:

       是两个对象实例!既然反射是先获得class(也是类的实例),再通过calss获得构造函数,去获取单例,那么解决办法就是在饿汉式构造函数中,同步类:

public class HungrySingLeton {// 创建HungrySingLeton 的一个对象private final static HungrySingLeton instance = new HungrySingLeton();// 让构造函数为private,这样该类就不会被实例化private HungrySingLeton() {// 防止防止反射破坏单例synchronized (HungrySingLeton.class) {if(instance != null){throw new RuntimeException("单例构造器禁止反射调用");}}}// 获取唯一可用的对象public static HungrySingLeton getInstance(){return instance;}/*** 获取对象的内存地址* @return*/public long getRamAddress() {return VM.current().addressOf(this);}
}

       测试结果如下:

5 总结

       饿汉式单例这种方式简单,也比较常用,在类创建的同时已经创建好一个静态的对象供系统使用,执行效率高。但这种方式下,因为还未调用对象就已经创建,造成资源的浪费,容易产生垃圾对象。

相关文章:

单例模式之饿汉式

目录 1 单例模式的程序结构 2 饿汉式单例模式的实现 3 饿汉式线程安全 4 防止反射破坏单例 5 总结 单例模式&#xff08;Singleton Pattern&#xff09;是 Java 中最简单的设计模式之一。所谓单例就是在系统中只有一个该类的实例&#xff0c;并且提供一个访问该实例的全局…...

软件测试培训三个月,找到工作了11K,面试总结分享给大家

功能方面&#xff1a;问的最多的就是测试流程&#xff0c;测试计划包含哪些内容&#xff0c;公司人员配置&#xff0c;有bug开发认为不是 bug怎么处理&#xff0c;怎样才算是好的用例&#xff0c;测试用例设计方法&#xff08;等价类&#xff0c;边界值等概念方法&#xff09;&…...

Hbase备份与恢复工具Snapshot的基本概念与工作原理

数据库都有相对完善的备份与恢复功能。备份与恢复功能是数据库在数据意外丢失、损坏下的最后一根救命稻草。数据库定期备份、定期演练恢复是当下很多重要业务都在慢慢接受的最佳实践&#xff0c;也是数据库管理者推荐的一种管理规范。HBase数据库最核心的备份与恢复工具——Sna…...

RTOS中事件集的实现原理以及实用应用

事件集的原理 RTOS中事件集的实现原理是通过位掩码来实现的。事件集是一种用于在任务之间传递信号的机制。在RTOS中&#xff0c;事件集通常是一个32位的二进制位向量。每个位都代表一个特定的事件&#xff0c;例如信号、标志、定时器等。 当一个任务等待一个或多个事件时&…...

计及新能源出力不确定性的电气设备综合能源系统协同优化(Matlab代码实现)

运行视频及运行结果&#xff1a; 计及碳排放成本的电-气-热综合能源系纷充节点能价计算方法研究&#xff08;Matlab代码实现&#xff09;目录 第一部分 文献一《计及新能源出力不确定性的电气设备综合能源系统协同优化》 0 引言 &#xff11; 新能源出力不确定性处理 1.1 新…...

推荐几个超实用的开源自动化测试框架

有什么好的开源自动化测试框架可以推荐&#xff1f;为了让大家看文章不蒙圈&#xff0c;文章我将围绕3个方面来阐述&#xff1a; 1、通用自动化测试框架介绍 2、Java语言下的自动化测试框架 3、Python语言下的自动化测试框架 随着计算机技术人员的大量增加&#xff0c;通过编写…...

Mac 上解压缩 RAR 文件

RAR 在十几年前的互联网曾叱咤风云般的存在。在那时&#xff0c;你所能见到的压缩文件几乎都是 RAR 格式&#xff0c;大家在 Windows 上使用的压缩、解压缩软件基本都是 WinRAR。虽然这些年使用 RAR 格式的压缩包的情况在逐渐减少&#xff0c;但是你还是经常能在国内各种网站下…...

C++核心编程<引用>(2)

c核心编程<引用>2.引用2.1引用的基本使用2.2引用注意事项2.3引用做函数参数2.4引用做函数返回值2.5引用的本质2.6常量引用2.引用 2.1引用的基本使用 作用: 给变量起别名语法:数据类型 &别名 原名演示#include<iostream> using namespace std; void func();i…...

零入门kubernetes网络实战-20->golang编程syscall操作tun设备介绍

《零入门kubernetes网络实战》视频专栏地址 https://www.ixigua.com/7193641905282875942 本篇文章视频地址(稍后上传) 本篇文章主要是使用golang自带的syscall包来创建tun类型的虚拟网络设备。 注意&#xff1a; 目前只能使用syscall包来创建tun类型的虚拟设备。 tun虚拟网…...

springboot之自动配置

文章目录前言一、配置文件及自动配置原理1、配置文件2、yaml1、注解注入方式给属性赋值2、yaml给实体类赋值3、Properties给属性赋值二、springboot的多环境配置四、自动配置总结前言 1、自动装配原理 2、多种方式给属性赋值 3、多环境配置 4、自动配置 一、配置文件及自动配置…...

wxpython设计GUI:wxFormBuilder工具常用布局结构介绍之布局四—面板拼接式

python借助wxFormBuilder工具搭建基础的GUI界面—wxFormBuilder工具使用介绍&#xff1a;https://blog.csdn.net/Logintern09/article/details/126685315 布局四&#xff1a;面板拼接式&#xff0c;先Panel面板构图&#xff0c;再使用程序代码在Frame框架上拼接面板 下面讲一下…...

全网最全之接口测试【加密解密攻防完整版】实战教程详解

看视频讲的更详细&#xff1a;https://www.bilibili.com/video/BV1zr4y1E7V5/? 一、对称加密 对称加密算法是共享密钥加密算法&#xff0c;在加密解密过程中&#xff0c;使用的密钥只有一个。发送和接收双方事先都知道加密的密钥&#xff0c;均使用这个密钥对数据进行加密和解…...

Python - 目录文件(OS模块) 常用操作

目录os模块的方法os.path()模块的方法使用示例示例一&#xff1a;简单使用示例二&#xff1a;获取文件夹下指定条件的文件os模块的方法 方法说明os.listdir(path)取得指定文件夹下的文件列表os.mkdir(path)创建一个名为path的文件夹os.open(file, flags)打开一个文件&#xff…...

把本地代码初始化到远程git仓库

本地代码&#xff0c;推送到远程的git仓库。第一种方法第一步&#xff1a;建立远程的git仓库第二步&#xff1a;拉取git仓库到本地第三步&#xff1a;将本地代码复制到本地的git拉下来的文件夹中第四步&#xff1a;代码提交即可git add . --> git commit -m 初始化 --> g…...

关于angular中的生命周期函数

生命周期函数&#xff0c;也叫生命周期钩子。 Angular的每个组件&#xff08;包括根组件和子组件&#xff09;都存在一个生命周期&#xff0c;从创建、更新、到销毁&#xff0c;Angular提供组件生命周期钩子函数&#xff0c; 组件的生命周期从实例化组件类并渲染组件视图及其…...

【拼图】拼图游戏-微信小程序开发流程详解

还记得小时候玩过的经典拼图游戏吗&#xff0c;上小学时&#xff0c;在路边摊用买个玩具&#xff0c;是一个正方形盒子形状&#xff0c;里面装的是图片分割成的很多块&#xff0c;还差一块&#xff0c;怎么描述好呢&#xff0c;和魔方玩具差不多&#xff0c;有没有听说叫二维的…...

第六章 opengl之光照(颜色)

OpenGL光照颜色创建一个光照场景光照 颜色 颜色由RGB组成&#xff0c;分别是红色&#xff0c;绿色&#xff0c;蓝色。举例定义一个颜色向量&#xff1a; glm::vec3 coral(1.0f, 0.5f, 0.31f);而在现实中&#xff0c;人眼看到的是 物体反射后的颜色&#xff0c;也就是说不能被…...

C语言-基础了解-19-C位域

C位域 一、C位域 如果程序的结构中包含多个开关量&#xff0c;只有 TRUE/FALSE 变量&#xff0c;如下&#xff1a; struct {unsigned int widthValidated;unsigned int heightValidated; } status;这种结构需要 8 字节的内存空间&#xff0c;但在实际上&#xff0c;在每个变…...

MapReduce全排序和二次排序

排序是MapReduce框架中最重要的操作之一。MapTask和ReduceTask均会对数据按照key进行排序。该操作属于Hadoop的默认行为。任何应用程序中的数据均会被排序&#xff0c;而不管逻辑上是否需要。默认排序是按照字典顺序排序&#xff0c;且实现该排序的方法是快速排序。对于MapTask…...

【Vue3】封装数字框组件

数量选择组件-基本结构 &#xff08;1&#xff09;准备基本结构 <script lang"ts" setup name"Numbox"> // </script> <template><div class"numbox"><div class"label">数量</div><div cla…...

黑马Mybatis

Mybatis 表现层&#xff1a;页面展示 业务层&#xff1a;逻辑处理 持久层&#xff1a;持久数据化保存 在这里插入图片描述 Mybatis快速入门 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/6501c2109c4442118ceb6014725e48e4.png //logback.xml <?xml ver…...

DAY 47

三、通道注意力 3.1 通道注意力的定义 # 新增&#xff1a;通道注意力模块&#xff08;SE模块&#xff09; class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...

【Go】3、Go语言进阶与依赖管理

前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课&#xff0c;做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程&#xff0c;它的核心机制是 Goroutine 协程、Channel 通道&#xff0c;并基于CSP&#xff08;Communicating Sequential Processes&#xff0…...

selenium学习实战【Python爬虫】

selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...

学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”

2025年#高考 将在近日拉开帷幕&#xff0c;#AI 监考一度冲上热搜。当AI深度融入高考&#xff0c;#时间同步 不再是辅助功能&#xff0c;而是决定AI监考系统成败的“生命线”。 AI亮相2025高考&#xff0c;40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕&#xff0c;江西、…...

Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换

目录 关键点 技术实现1 技术实现2 摘要&#xff1a; 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式&#xff08;自动驾驶、人工驾驶、远程驾驶、主动安全&#xff09;&#xff0c;并通过实时消息推送更新车…...

给网站添加live2d看板娘

给网站添加live2d看板娘 参考文献&#xff1a; stevenjoezhang/live2d-widget: 把萌萌哒的看板娘抱回家 (ノ≧∇≦)ノ | Live2D widget for web platformEikanya/Live2d-model: Live2d model collectionzenghongtu/live2d-model-assets 前言 网站环境如下&#xff0c;文章也主…...

高防服务器价格高原因分析

高防服务器的价格较高&#xff0c;主要是由于其特殊的防御机制、硬件配置、运营维护等多方面的综合成本。以下从技术、资源和服务三个维度详细解析高防服务器昂贵的原因&#xff1a; 一、硬件与技术投入 大带宽需求 DDoS攻击通过占用大量带宽资源瘫痪目标服务器&#xff0c;因此…...

Linux入门(十五)安装java安装tomcat安装dotnet安装mysql

安装java yum install java-17-openjdk-devel查找安装地址 update-alternatives --config java设置环境变量 vi /etc/profile #在文档后面追加 JAVA_HOME"通过查找安装地址命令显示的路径" #注意一定要加$PATH不然路径就只剩下新加的路径了&#xff0c;系统很多命…...

Android Framework预装traceroute执行文件到system/bin下

文章目录 Android SDK中寻找traceroute代码内置traceroute到SDK中traceroute参数说明-I 参数&#xff08;使用 ICMP Echo 请求&#xff09;-T 参数&#xff08;使用 TCP SYN 包&#xff09; 相关文章 Android SDK中寻找traceroute代码 设备使用的是Android 11&#xff0c;在/s…...