Java设计模式:单例模式之六种实现方式详解(二)
在Java中,单例模式是一种常见的设计模式,用于确保一个类只有一个实例,并提供一个全局访问点来获取该实例。单例模式在多种场景下都很有用,比如配置文件的读取、数据库连接池、线程池等。本文将详细介绍Java中实现单例模式的六种方式,并分析它们的原理和优缺点。
目录
- 单例模式概述
- 1. 饿汉式
- 2. 懒汉式(线程不安全)
- 3. 懒汉式(线程安全)
- 4. 双重检查锁定(DCL)
- 5. 静态内部类
- 6. 枚举
- 单例模式的使用场景
- 注意事项
- 总结
单例模式概述
单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目标是确保一个类只有一个实例,并提供一个全局访问点来获取该实例。在单例模式中,类的构造函数通常是私有的,以防止其他类实例化它。同时,该类提供一个静态方法或属性来获取该类的唯一实例。
单例模式的应用场景包括需要频繁进行创建和销毁的对象、需要共享资源的情况以及需要全局唯一访问点的场景等。通过使用单例模式,可以节省系统资源、提高性能并避免对共享资源的多重占用。
在Java等面向对象的编程语言中,单例模式通常通过以下方式实现:
- 饿汉式单例(在类加载时就完成了初始化,所以类加载比较慢,但获取对象的速度快
- 懒汉式单例(类加载时不初始化,第一次调用时才初始化,并且需要考虑线程安全的问题)
- 双重检查锁定(DCL,即懒汉式的优化,可以减少部分不必要的同步)
- 静态内部类(利用了classloader的机制来保证初始化instance时只有一个线程
- 枚举(不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象)等。
使用单例模式时需要注意线程安全、反序列化问题、反射攻击等问题。此外,在设计时也需要考虑其可扩展性,以便在未来需要支持多个实例或动态创建实例时能够方便地进行修改。
1. 饿汉式
原理:在类加载时就完成了初始化,所以类加载比较慢,但获取对象的速度快。因为类加载时就完成了初始化,所以天生就是线程安全的。
public class Singleton {private static Singleton instance = new Singleton();private Singleton() {}public static Singleton getInstance() {return instance;}
}
优点:实现简单,线程安全。
缺点:如果单例从未被使用,则会造成内存的浪费。
2. 懒汉式(线程不安全)
原理:类加载时不初始化,第一次调用getInstance()方法时才创建实例。这种方式在多线程环境下是不安全的,可能会导致创建多个实例。
public class Singleton {private static Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}
优点:避免了饿汉式的内存浪费问题。
缺点:线程不安全,可能导致创建多个实例。
3. 懒汉式(线程安全)
原理:在getInstance()方法上加同步锁,确保在多线程环境下只创建一个实例。
public class Singleton {private static Singleton instance;private Singleton() {}public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}
优点:线程安全。
缺点:每次调用getInstance()方法时都要进行同步,效率较低。
4. 双重检查锁定(DCL)
原理:通过双重检查锁定(DCL)来减少同步的开销。只有当instance为null时,才进行同步块的加锁操作。
public class Singleton {private volatile static Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}
注意:在Java 1.5以前的版本中,双重检查锁定由于JVM的内存模型原因可能会出现问题,但在Java 1.5及以上版本中,通过volatile关键字和内存模型的改进,双重检查锁定已经被正确地实现。
优点:线程安全,且效率较高。
缺点:实现相对复杂,容易出错。
5. 静态内部类
原理:利用静态内部类的特性,当外部类被加载时,静态内部类不会被加载,只有当调用静态内部类时才会被加载,从而实现了懒加载。同时,静态内部类的加载是线程安全的。
public class Singleton {private Singleton() {}private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}
优点:线程安全,且实现了懒加载。
缺点:与饿汉式相比,增加了一定的代码复杂性。
6. 枚举
原理:利用枚举的特性,Java枚举在创建时是线程安全的,并且只会装载一次。
当使用枚举来实现单例模式时,代码实际上非常简单和直接。枚举在Java中是一种特殊的类,它有一组预定义的常量。由于Java枚举的特性,它们在创建时是线程安全的,并且只会装载一次,这使得它们成为实现单例模式的一种有效方式。
public enum Singleton {// 枚举常量,它本身就是单例的实例INSTANCE;// 单例对象可以拥有方法public void someMethod() {// 这里是单例对象可以执行的操作System.out.println("Doing something in the singleton instance.");}
}// 使用示例
public class SingletonDemo {public static void main(String[] args) {// 获取单例对象Singleton singleton = Singleton.INSTANCE;// 调用单例对象的方法singleton.someMethod();// 尝试再次获取单例对象,将会得到与之前相同的实例Singleton anotherSingleton = Singleton.INSTANCE;// 检查两个引用是否指向同一个对象System.out.println("singleton == anotherSingleton: " + (singleton == anotherSingleton));}
}
在这个例子中,Singleton是一个枚举类,它只有一个枚举常量INSTANCE。这个常量就是单例对象的唯一实例。你可以在Singleton枚举类中定义任何你需要的方法,就像在一个普通的Java类中那样。
当你通过Singleton.INSTANCE来访问单例对象时,Java虚拟机保证你总是得到同一个实例,因为枚举常量在加载时就被创建,并且是唯一的。
使用枚举实现单例模式的好处之一是它自动支持序列化机制,即使你将枚举实例序列化后再反序列化,得到的仍然是同一个实例。这是由Java枚举的序列化特性保证的。此外,枚举方式还防止了反射攻击,因为尝试通过反射来调用枚举的私有构造器将会抛出异常。
使用方式:Singleton.INSTANCE.someMethod();
优点:实现简单,线程安全,且自动支持序列化机制,防止反序列化重新创建新的对象。
缺点:无法进行懒加载。如果其他单例模式的实现有序列化需求时,需要注意防止反序列化导致的重新创建对象的问题。而枚举类型则没有这个问题。
单例模式的使用场景
-
全局访问点:当需要提供一个全局唯一的访问点来访问某个资源或服务时,单例模式非常适用。例如,配置文件的管理、数据库连接池、线程池、日志记录器等。
-
资源控制:单例模式可用于控制对共享资源的并发访问,如文件、网络连接或数据库连接。通过确保只有一个实例存在,可以避免不必要的资源消耗和冲突。
-
状态维护:如果需要在整个应用程序生命周期中维护某个状态或信息,并且这个状态不会因多个实例的创建而改变,那么单例模式是一个很好的选择。
-
节省系统资源:对于创建开销较大的对象或资源密集型对象,使用单例模式可以确保只创建一个实例,从而节省系统资源。
-
工具类:一些工具类,如数学计算工具、日期格式化工具等,通常不需要多个实例,可以使用单例模式来实现。
注意事项
-
线程安全:在多线程环境下,确保单例模式的实现是线程安全的。可以使用双重检查锁定(DCL)、静态内部类、枚举等方式来实现线程安全的单例。
-
反序列化问题:如果单例对象实现了
Serializable接口,那么需要注意反序列化时可能会创建新的实例。为了解决这个问题,可以在readResolve()方法中返回单例对象。 -
反射攻击:尽管Java语言提供了访问修饰符来限制类的实例化,但通过反射机制仍然可以调用私有构造器。因此,需要注意防止通过反射破坏单例模式的约束。可以通过在私有构造器中检查已存在实例并抛出异常来防止这种情况。
-
垃圾回收:如果单例对象持有对外部对象的引用,并且这些外部对象不再需要时没有被正确释放,那么可能会导致内存泄漏。因此,需要确保单例对象在不再需要时能够释放其持有的资源。
-
测试问题:在单元测试中,单例模式可能会导致测试之间的依赖和顺序问题。为了避免这种情况,可以考虑使用依赖注入或模拟框架来替换单例对象。
-
可扩展性:设计单例模式时需要考虑其可扩展性。如果需要支持多个实例或动态创建实例,那么单例模式可能不是最佳选择。在这种情况下,可以考虑使用工厂模式或原型模式等替代方案。
总结
在选择单例模式的实现方式时,需要根据具体的应用场景和需求进行权衡。如果单例对象在程序启动时就需要被创建且不会造成内存浪费,可以选择饿汉式;如果需要实现懒加载,并且对线程安全性有要求,可以选择静态内部类或枚举;如果需要在懒加载的同时还要追求极致的性能,可以尝试双重检查锁定,但需要注意其实现的复杂性。懒汉式(线程不安全)通常不推荐使用,因为它在多线程环境下可能会导致问题。懒汉式(线程安全)虽然简单,但由于其效率较低,也不常作为首选方案。
相关文章:
Java设计模式:单例模式之六种实现方式详解(二)
在Java中,单例模式是一种常见的设计模式,用于确保一个类只有一个实例,并提供一个全局访问点来获取该实例。单例模式在多种场景下都很有用,比如配置文件的读取、数据库连接池、线程池等。本文将详细介绍Java中实现单例模式的六种方…...
开创5G无线新应用:笙科电子5.8GHz 射频芯片
笙科电子(AMICCOM) 5.8GHz A5133射频芯片是一款专门设计用于在5.8GHz频率范围内(5725MHz - 5850MHz)进行射频信号处理的集成电路。这些集成电路通常包括各种功能模块,如射频前端、混合器、功率放大器、局部振荡器等,以支持无线通信系统的各种…...
使用 JMeter 生成测试数据对 MySQL 进行压力测试
博主历时三年精心创作的《大数据平台架构与原型实现:数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行,点击《重磅推荐:建大数据平台太难了!给我发个工程原型吧!》了解图书详情,…...
C# cass10 面积计算
运行环境Visual Studio 2022 c# cad2016 cass10 通过面积计算得到扩展数据,宗地面积 ,房屋占地面积,房屋使用面积 一、主要步骤 获取当前AutoCAD应用中的活动文档、数据库和编辑器对象。创建一个选择过滤器,限制用户只能选择&q…...
中间件-Nginx漏洞整改(限制IP访问隐藏nginx版本信息)
中间件-Nginx漏洞整改(限制IP访问&隐藏nginx版本信息) 一、限制IP访问1.1 配置Nginx的ACL1.2 重载Nginx配置1.3 验证结果 二、隐藏nginx版本信息2.1 打开Nginx配置文件2.2 隐藏Nginx版本信息2.3 保存并重新加载Nginx配置2.4 验证结果2.5 验证隐藏版本…...
Xcode与Swift开发小记
文章目录 引子Xcode工程结构核心概念Swift语法速记(TODO)小技巧单元测试中使用awaitSwiftUI中使用ListView中取数据 常见问题Xcode添加package时连接github超时Xcode无法修改快捷键,一闪而过 引子 鉴于React Native目前版本在iOS上开发遇到诸多问题,本以…...
日更【系统架构设计师知识总结3】存储系统
【原创精华总结】自己一点点手打、总结的脑图,把散落在课本以及老师讲授的知识点合并汇总,反复提炼语言,形成知识框架。希望能给同样在学习的伙伴一点帮助!...
《TCP/IP详解 卷一》第7章 防火墙和NAT
7.1 引言 NAT通常改变源IP和源端口,不改变目的IP和目的端口。 7.2 防火墙 常用防火墙: 包过滤防火墙(packet-filter firewall) 代理防火墙(proxy firewall) 代理防火墙作用: 1. 通过代理服务…...
访问raw.githubusercontent.com失败问题的处理
1 问题 GitHub上的项目的有些资源是放在raw.githubusercontent.com上的,通常我们在安装某些软件的时候会从该地址下载资源,直接访问的话经常容易失败。 # 安装operator kubectl apply -f https://raw.githubusercontent.com/oceanbase/ob-operator/2.1…...
Elasticsearch的基本安装教程,Elasticsearch+SpringBoot实现简单的增删改查功能
Elasticsearch 是一个开源的分布式搜索和分析引擎,最初由 Elastic 公司开发。它是基于 Apache Lucene 的搜索引擎构建的,提供了强大的搜索和分析功能,并支持实时数据检索和分析。 Elasticsearch 被设计用来处理大规模的数据集,它具有以下几个主要特点: 分布式架构: Elast…...
【Git教程】(五)分支 —— 并行式开发,分支相关操作(创建、切换、删除)~
Git教程 分支 1️⃣ 并行式开发2️⃣ 修复旧版本中的 bug3️⃣ 分支4️⃣ 当前活跃分支5️⃣ 重置分支指针6️⃣ 删除分支7️⃣ 清理提交对象🌾 总结 对于版本提交为什么不能依次进行,以便形成一条直线型的提交历史记录,我们认为有 以下两个…...
Maven管理项目,本地仓库有对应的jar包,但还是报找不到
文章目录 业务场景错误提示分析过程解决办法 业务场景 settings.xml种配置了私服,但是有些依赖私服上没有,通过同事拷贝过来的。但是用maven打包时报红了。 错误提示 Idea Maven错误:was cached in the local repository, resolution will…...
手写JavaScript中的Promise.all方法(JS中Promise.all的执行过程)
简介: Promise.all是JavaScript中一种用于处理多个Promise对象的方法,该方法接收一个数组作为参数,并返回一个新的Promise对象。 这个新的对象会在所有Promise对象都成功解析后解析,解析的结果是一个数组,包含了所有P…...
IP设置教程
Win 7 固定Ip设定 https://jingyan.baidu.com/article/4b07be3cbc8e7348b380f31d.html Win 10 固定Ip设定 Win10 固定IP地址方法_win10设置固定ip地址怎么设置-CSDN博客 Win 11 固定Ip设定 https://jingyan.baidu.com/article/cb5d6105be5354415c2fe0d3.html TP-LINK…...
【Azure 架构师学习笔记】-Azure Synapse -- Link for SQL 实时数据加载
本文属于【Azure 架构师学习笔记】系列。 本文属于【Azure Synapse】系列。 前言 Azure Synapse Link for SQL 可以提供从SQL Server或者Azure SQL中接近实时的数据加载。通过这个技术,使用SQL Server/Azure SQL中的新数据能够几乎实时地传送到Synapse(…...
k8s(5)
目录 使用Kubeadm安装k8s集群: 初始化操作: 每台主从节点: 升级内核: 所有节点安装docker : 所有节点安装kubeadm,kubelet和kubectl: 修改了 kubeadm-config.yaml,将其传输给…...
【服务器数据恢复】ext3文件系统下硬盘坏道掉线的数据恢复案例
服务器数据恢复环境: 一台IBM某型号服务器上有16块FC硬盘组建RAID阵列。上层linux操作系统,ext3文件系统,部署有oracle数据库。 服务器故障&检测: 服务器上跑的业务突然崩溃,管理员发现服务器上有2块磁盘的指示灯…...
Sentinel 动态规则扩展
一、规则 Sentinel 的理念是开发者只需要关注资源的定义,当资源定义成功后可以动态增加各种流控降级规则。Sentinel 提供两种方式修改规则: 通过 API 直接修改 (loadRules)通过 DataSource 适配不同数据源修改 手动通过 API 修改比较直观,…...
UE5 UE4 自定义插件自动开启关联插件(plugin enable)
在我们自己编写UE4、UE5的插件时,常常需要开启相关联的插件进行功能编写。 例如:UE4/5 批量进行贴图Texture压缩、修改饱和度_ue4批量修改纹理大小-CSDN博客 而让插件使用者每次使用时,依次进行开启其他相关联插件确实有些麻烦。 如何只需要…...
Android摄像头横屏的时候_人脸预览横向显示_问题解决---Android原生开发工作笔记164
在Android系统的平板中发现一个问题,我们做的一个Android程序,横屏的时候,摄像头在上面, 然后这个时候程序中的一个人脸预览页面,横向手持平板,摄像头在上面,但是这个时候预览的摄像头画面却是很像头像朝左,也是横过来的. private int getCameraOrientation(int cameraId) {Ca…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...
vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...
HDFS分布式存储 zookeeper
hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架,允许使用简单的变成模型跨计算机对大型集群进行分布式处理(1.海量的数据存储 2.海量数据的计算)Hadoop核心组件 hdfs(分布式文件存储系统)&a…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...
作为测试我们应该关注redis哪些方面
1、功能测试 数据结构操作:验证字符串、列表、哈希、集合和有序的基本操作是否正确 持久化:测试aof和aof持久化机制,确保数据在开启后正确恢复。 事务:检查事务的原子性和回滚机制。 发布订阅:确保消息正确传递。 2、性…...
WebRTC从入门到实践 - 零基础教程
WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC? WebRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音…...
