单例模式的写法(保证线程安全)

1. 引言

1.1 什么是单例模式?

单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点
核心思想:控制实例化过程,避免重复创建对象。

1.2 为什么需要单例模式?

  • 节省资源:某些对象(如数据库连接池、日志管理器)只需要一个实例。

  • 全局访问:方便在多个模块中共享数据。

  • 避免冲突:如配置文件管理,防止多个实例修改导致不一致。

1.3 线程安全的重要性

  • 多线程环境下,如果不加控制,可能导致:

    • 创建多个实例(违反单例原则)。

    • 对象状态不一致(如缓存数据被覆盖)。

  • 目标:确保在任何情况下,单例类只被初始化一次


2. 单例模式的实现方式

2.1 饿汉式(Eager Initialization)

代码实现
public class Singleton {// 类加载时就初始化(线程安全由JVM保证)private static final Singleton INSTANCE = new Singleton();// 私有构造方法,防止外部newprivate Singleton() {}// 全局访问点public static Singleton getInstance() {return INSTANCE;}
}
特点
  • 优点

    • 实现简单,线程安全(由类加载机制保证)。

    • 没有锁,性能高。

  • 缺点

    • 即使不用也会创建实例,可能浪费内存。

    • 无法传递参数初始化(如需要动态配置)。

适用场景
  • 实例占用内存小,且一定会被使用(如简单配置类)。


2.2 懒汉式(Lazy Initialization)

基础版(非线程安全)
public class Singleton {private static Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {instance = new Singleton(); // 多线程下可能创建多个实例}return instance;}
}

问题:多线程环境下,多个线程可能同时进入 if (instance == null),导致多次实例化。


加锁版(线程安全但低效)
public class Singleton {private static Singleton instance;private Singleton() {}// 方法加锁,保证线程安全public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

问题:每次调用 getInstance() 都会加锁,性能差(99%的情况不需要同步)。


2.3 双重检查锁(Double-Checked Locking, DCL)

代码实现
public class Singleton {// volatile 禁止指令重排序,避免半初始化问题private static volatile Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) { // 第一次检查(无锁,提高性能)synchronized (Singleton.class) {if (instance == null) { // 第二次检查(防止多线程竞争)instance = new Singleton();}}}return instance;}
}
关键点
  1. volatile 的作用

    • 防止指令重排序(避免返回未初始化的对象)。

    • 保证可见性(一个线程修改后,其他线程立即可见)。

  2. 两次判空

    • 第一次检查(无锁):提高性能,避免每次加锁。

    • 第二次检查(加锁):防止多线程竞争导致多次实例化。

优点
  • 线程安全 + 高性能(只有第一次初始化需要同步)。

缺点
  • 代码稍复杂,需理解 volatile 和指令重排序。


2.4 静态内部类(Holder Class)

代码实现
public class Singleton {private Singleton() {}// 静态内部类(延迟加载)private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance() {return SingletonHolder.INSTANCE; // 调用时才加载}
}
原理
  • 类加载机制SingletonHolder 只有在 getInstance() 被调用时才会加载。

  • 线程安全:由 JVM 保证静态内部类的初始化是线程安全的。

优点
  • 线程安全 + 延迟加载 + 无锁高性能。

  • 比双重检查锁更简洁。

缺点
  • 无法传递参数初始化(适合无参构造)。


2.5 枚举(Enum Singleton)

代码实现
public enum Singleton {INSTANCE; // 单例实例public void doSomething() {System.out.println("Singleton method");}
}
原理
  • JVM 保证枚举唯一性:枚举实例在类加载时初始化,且不可反射创建。

优点
  1. 绝对线程安全(JVM 保证)。

  2. 防止反射攻击(无法通过反射创建新实例)。

  3. 防止反序列化破坏(枚举天然支持 readResolve)。

缺点
  • 不能延迟加载(类加载时就初始化)。

  • 写法稍特殊(部分开发者不习惯)。


3. 如何选择单例模式?

实现方式线程安全延迟加载防止反射防止反序列化性能适用场景
饿汉式⭐⭐⭐简单场景
懒汉式(同步)不推荐
双重检查锁⭐⭐⭐高并发
静态内部类⭐⭐⭐推荐
枚举⭐⭐⭐最佳实践

推荐选择

  1. 简单场景 → 饿汉式。

  2. 需要延迟加载 → 静态内部类。

  3. 高并发 + 参数初始化 → 双重检查锁。

  4. 最佳实践 → 枚举单例(安全、简洁)。


4. 单例模式的破坏与防御

4.1 反射攻击

问题
Singleton instance1 = Singleton.getInstance();
// 反射调用私有构造方法
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton instance2 = constructor.newInstance(); // 破坏单例
防御(非枚举类)
private Singleton() {if (INSTANCE != null) {throw new RuntimeException("Use getInstance() method!");}
}

枚举天然防御反射(JVM 保证唯一性)。


4.2 反序列化破坏

问题
// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.ser"));
oos.writeObject(Singleton.getInstance());
// 反序列化(会调用readObject,可能创建新实例)
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.ser"));
Singleton instance2 = (Singleton) ois.readObject(); // 可能破坏单例
防御(非枚举类)
// 添加readResolve方法,返回单例实例
protected Object readResolve() {return getInstance();
}

枚举天然防御反序列化(JVM 保证唯一性)。


5. 总结

  1. 线程安全是关键:多线程环境下必须保证单例唯一。

  2. 推荐实现

    • 枚举单例(简洁、安全、防反射)。

    • 静态内部类(延迟加载、高性能)。

    • 双重检查锁(适合需要参数初始化的场景)。

  3. 避免

    • 不加锁的懒汉式(线程不安全)。

    • 方法级同步懒汉式(性能差)。

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

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

相关文章

C++ 环境设置

C++ 环境设置 引言 C++作为一种高性能的编程语言,广泛应用于系统软件、游戏开发、实时系统等领域。为了能够顺利进行C++编程,我们需要在计算机上配置合适的开发环境。本文将详细讲解如何在Windows、macOS和Linux系统中设置C++开发环境。 Windows系统下C++环境设置 1. 安装…

【Kafka基础】ZooKeeper在Kafka中的核心作用:分布式系统中枢神经系统

在分布式系统的世界里&#xff0c;协调和管理多个节点间的状态是一项复杂而关键的任务。Apache Kafka作为一款高性能的分布式消息系统&#xff0c;其设计哲学是"专为单一目的而优化"——即高效处理消息流。为了实现这一目标&#xff0c;Kafka选择将集群协调管理的重任…

<《AI大模型应知应会100篇》第8篇:大模型的知识获取方式及其局限性

第8篇&#xff1a;大模型的知识获取方式及其局限性 摘要 大模型&#xff08;如GPT、BERT、Qwen、DeepSeek等&#xff09;凭借其卓越的自然语言处理能力&#xff0c;已经成为人工智能领域的明星。然而&#xff0c;这些模型“知道”什么&#xff1f;它们如何获取知识&#xff1f…

ESModule和CommonJS在Node中的区别

ESModule console.log(require);//>errorconsole.log(module);//>errorconsole.log(exports);//>errorconsole.log(__filename);//>errorconsole.log(__dirname);//>error全部报错commonjs console.log(require);console.log(module);console.log(exports);co…

Spring Boot 配置文件加载优先级全解析

精心整理了最新的面试资料和简历模板&#xff0c;有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 Spring Boot 配置文件加载优先级全解析 Spring Boot 的配置文件加载机制是开发者管理不同环境配置的核心功能之一。其通过外部化配置&#xff08;Externaliz…

2025 年陕西消防设施操作员考试攻略:历史文化名城的消防传承与创新​

陕西拥有丰富的历史文化遗产&#xff0c;众多古建筑分布其中&#xff0c;同时也在不断推进现代化建设&#xff0c;消防工作面临传承与创新的双重任务&#xff0c;这在考试中也有所体现。​ 考点融合与特色&#xff1a;一方面&#xff0c;古建筑的消防保护是重点&#xff0c;包…

【Unity网络编程知识】C#的 Http相关类学习

1、搭建HTTP服务器 使用别人做好的HTTP服务器软件&#xff0c;一般作为资源服务器时使用该方式&#xff08;学习阶段建议使用&#xff09;自己编写HTTP服务器应用程序&#xff0c;一般作为Web服务器或者短连接游戏服务器时使用该方式&#xff08;工作后由后端程序员来做&#…

Android Studio - 解决 Please Select Android SDK

一、出现的问题 点击 Run 后弹窗&#xff0c;图一位置出现图二提示。 二、解决办法 进入 Tools -> SDK Manager&#xff0c;在 Android SDK Location 点击 Edit&#xff0c;一直 Next 就解决了。

UE5学习笔记 FPS游戏制作44 统一UI大小 sizeBox

如果我们希望多个类似的UI大小一样&#xff0c;例如不同菜单的标题&#xff0c;可以使用sizeBox组件 我们在标题控件上&#xff0c;用sizeBox包裹所有子物体 然后指定他的最小宽高&#xff0c;或最大宽高 如果指定的是最小宽高&#xff0c;当子元素&#xff08;如图片&#xf…

MCP协议介绍

MCP协议&#xff08;Model Context Protocol&#xff0c;模型上下文协议&#xff09;是由Anthropic公司推出的开放协议&#xff0c;旨在为AI大模型与外部数据源、工具之间建立标准化交互框架。其核心价值在于突破传统API限制&#xff0c;通过统一接口实现AI与多源数据、工具的双…

C#里使用WPF的MaterialDesignThemes

先要下载下面的包: <?xml version="1.0" encoding="utf-8"?> <packages><package id="MaterialDesignColors" version="5.2.1" targetFramework="net48" /><package id="MaterialDesignTheme…

基于 Spring Boot 瑞吉外卖系统开发(四)

基于 Spring Boot 瑞吉外卖系统开发&#xff08;四&#xff09; 新增分类 新增分类UI界面&#xff0c;两个按钮分别对应两个UI界面 两个页面所需的接口都一样&#xff0c;请求参数type值不一样&#xff0c;type1为菜品分类&#xff0c;type2为套餐分类。 请求方法都为POST。…

神经网络 | 基于脉冲耦合神经网络PCNN图像特征提取与匹配(附matlab代码)

内容未发表论文基于脉冲耦合神经网络(PCNN)的图像特征提取与匹配研究 摘要 本文提出一种基于脉冲耦合神经网络(Pulse-Coupled Neural Network, PCNN)的图像特征提取与匹配方法。通过模拟生物视觉皮层神经元的脉冲同步发放特性,PCNN能够有效捕捉图像纹理与边缘特征。实验表…

LeetCode 252 会议室题全解析:Swift 实现 + 场景还原

文章目录 摘要描述题解答案题解代码分析示例测试及结果时间复杂度空间复杂度总结 摘要 在这篇文章中&#xff0c;我们将深入探讨LeetCode第252题“会议室”的问题&#xff0c;提供一个用Swift编写的解决方案&#xff0c;并结合实际场景进行分析。通过这篇文章&#xff0c;你将…

HBuilder运行uni-app程序报错【Error: listen EACCES: permission denied 0.0.0.0:5173】

一、错误提示&#xff1a; 当使用HBuilder运行uni-app项目的时候提示了如下错误❌ 15:11:03.089 项目 project 开始编译 15:11:04.404 请注意运行模式下&#xff0c;因日志输出、sourcemap 以及未压缩源码等原因&#xff0c;性能和包体积&#xff0c;均不及发行模式。 15:11:04…

Flink框架:批处理和流式处理与有界数据和无界数据之间的关系

本文重点 从数据集的类型来看&#xff0c;数据集可以分为有界数据和无界数据两种&#xff0c;从处理方式来看&#xff0c;有批处理和流处理两种。一般而言有界数据常常使用批处理方式&#xff0c;无界数据往往使用流处理方式。 有界数据和无界数据 有界数据有一个明确的开始和…

虚拟列表react-virtualized使用(npm install react-virtualized)

1. 虚拟化列表 (List) // 1. 虚拟化列表 (List)import { List } from react-virtualized; import react-virtualized/styles.css; // 只导入一次样式// 示例数据 const list Array(1000).fill().map((_, index) > ({id: index,name: Item ${index},description: This is i…

IT+开发+业务一体化:AI驱动的ITSM解决方案Jira Service Management价值分析(文末免费获取报告)

本文来源atlassian.com&#xff0c;由Atlassian全球白金合作伙伴、DevSecOps解决方案提供商-龙智翻译整理。 无论是支持内部员工、处理突发事件还是批准变更申请&#xff0c;服务团队的每一分钟都至关重要。您的企业是否做好了充分准备&#xff1f; 许多企业仍然依赖传统的IT服…

leetcode刷题日记——167. 两数之和 II - 输入有序数组

[ 题目描述 ]&#xff1a; [ 思路 ]&#xff1a; 题目要求求数值numbers中的和为 target 的两个数的下标最简单的思路就是暴力求解&#xff0c;两两挨个组合&#xff0c;但时间复杂度为O(n2)&#xff0c;不一定能通过因为数组为非递减&#xff0c;那我们可以使用双指针&#…

【Leetcode-Hot100】盛最多水的容器

题目 解答 目的是求面积最大&#xff0c;面积是由两个下标和对应的最小值得到&#xff0c;因此唯一的问题就是如何遍历这两个下标。我采用begin和end两个变量&#xff0c;确保begin是小于end的&#xff0c;使用它们二者求面积&#xff0c;代码如下&#xff1a; 很不幸 出错了…