单例模式之饿汉式
目录
1 单例模式的程序结构
2 饿汉式单例模式的实现
3 饿汉式线程安全
4 防止反射破坏单例
5 总结
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。所谓单例就是在系统中只有一个该类的实例,并且提供一个访问该实例的全局访问方法。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式,关于单例模式的原理,可参考文末的链接文章。
单例的实现分为三个步骤:
- 构造方法私有化。即不能在类外实例化,只能在类内实例化。
- 在本类中完成自身的初始化,自己创建本类的实例,且是唯一的实例。
- 在本类中提供给外部获取实例的方式,提供访问该实例的全局静态方法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 总结 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。所谓单例就是在系统中只有一个该类的实例,并且提供一个访问该实例的全局…...

软件测试培训三个月,找到工作了11K,面试总结分享给大家
功能方面:问的最多的就是测试流程,测试计划包含哪些内容,公司人员配置,有bug开发认为不是 bug怎么处理,怎样才算是好的用例,测试用例设计方法(等价类,边界值等概念方法)&…...

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

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

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

推荐几个超实用的开源自动化测试框架
有什么好的开源自动化测试框架可以推荐?为了让大家看文章不蒙圈,文章我将围绕3个方面来阐述: 1、通用自动化测试框架介绍 2、Java语言下的自动化测试框架 3、Python语言下的自动化测试框架 随着计算机技术人员的大量增加,通过编写…...
Mac 上解压缩 RAR 文件
RAR 在十几年前的互联网曾叱咤风云般的存在。在那时,你所能见到的压缩文件几乎都是 RAR 格式,大家在 Windows 上使用的压缩、解压缩软件基本都是 WinRAR。虽然这些年使用 RAR 格式的压缩包的情况在逐渐减少,但是你还是经常能在国内各种网站下…...
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类型的虚拟网络设备。 注意: 目前只能使用syscall包来创建tun类型的虚拟设备。 tun虚拟网…...

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

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

全网最全之接口测试【加密解密攻防完整版】实战教程详解
看视频讲的更详细:https://www.bilibili.com/video/BV1zr4y1E7V5/? 一、对称加密 对称加密算法是共享密钥加密算法,在加密解密过程中,使用的密钥只有一个。发送和接收双方事先都知道加密的密钥,均使用这个密钥对数据进行加密和解…...
Python - 目录文件(OS模块) 常用操作
目录os模块的方法os.path()模块的方法使用示例示例一:简单使用示例二:获取文件夹下指定条件的文件os模块的方法 方法说明os.listdir(path)取得指定文件夹下的文件列表os.mkdir(path)创建一个名为path的文件夹os.open(file, flags)打开一个文件ÿ…...
把本地代码初始化到远程git仓库
本地代码,推送到远程的git仓库。第一种方法第一步:建立远程的git仓库第二步:拉取git仓库到本地第三步:将本地代码复制到本地的git拉下来的文件夹中第四步:代码提交即可git add . --> git commit -m 初始化 --> g…...
关于angular中的生命周期函数
生命周期函数,也叫生命周期钩子。 Angular的每个组件(包括根组件和子组件)都存在一个生命周期,从创建、更新、到销毁,Angular提供组件生命周期钩子函数, 组件的生命周期从实例化组件类并渲染组件视图及其…...

【拼图】拼图游戏-微信小程序开发流程详解
还记得小时候玩过的经典拼图游戏吗,上小学时,在路边摊用买个玩具,是一个正方形盒子形状,里面装的是图片分割成的很多块,还差一块,怎么描述好呢,和魔方玩具差不多,有没有听说叫二维的…...
第六章 opengl之光照(颜色)
OpenGL光照颜色创建一个光照场景光照 颜色 颜色由RGB组成,分别是红色,绿色,蓝色。举例定义一个颜色向量: glm::vec3 coral(1.0f, 0.5f, 0.31f);而在现实中,人眼看到的是 物体反射后的颜色,也就是说不能被…...

C语言-基础了解-19-C位域
C位域 一、C位域 如果程序的结构中包含多个开关量,只有 TRUE/FALSE 变量,如下: struct {unsigned int widthValidated;unsigned int heightValidated; } status;这种结构需要 8 字节的内存空间,但在实际上,在每个变…...

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

【Vue3】封装数字框组件
数量选择组件-基本结构 (1)准备基本结构 <script lang"ts" setup name"Numbox"> // </script> <template><div class"numbox"><div class"label">数量</div><div cla…...

第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
MVC 数据库
MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...

Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...

springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...

使用LangGraph和LangSmith构建多智能体人工智能系统
现在,通过组合几个较小的子智能体来创建一个强大的人工智能智能体正成为一种趋势。但这也带来了一些挑战,比如减少幻觉、管理对话流程、在测试期间留意智能体的工作方式、允许人工介入以及评估其性能。你需要进行大量的反复试验。 在这篇博客〔原作者&a…...