初识单例模式

文章目录

    • 场景通点
    • 定义
      • 实现思路
    • 六种 Java 实现
      • 饿汉式
      • 懒汉式
      • synchronized 方法
      • 双重检查锁 Double Check Lock + Volatile
      • 静态内部类 Singleton Holder
      • 枚举单例
    • 单例运用场景
    • 破解单例模式
    • 参考

场景通点

  • 资源昂贵:数据库连接池、线程池、日志组件,只需要一份全局对象,频繁 new 会导致资源浪费。
  • 全局一致性:配置中心、缓存目录、全局计数器等需要强唯一,避免状态错乱。
  • 集中管理:方便做“生命周期”管控(初始化、销毁)。
  • 日志、配置中心、线程池、连接池、注册表、全局序列号生成器等 无状态或少状态、需要唯一性的组件
  • 资源较重而复用频繁

定义

保证一个类只有一个实例,并提供一个全局访问点

  • “只有一个”➜ 私有构造器 + 类级别的持有者(静态字段)
  • “全局访问”➜ 公共静态方法 (getInstance())

实现思路

  • 静态化实例对象, 让实例对象与 Class 对象互相绑定, 通过 Class 类对象就可以直接访问
  • 私有化构造方法, 禁止通过构造方法创建多个实例 —— 最重要的一步
  • 提供一个公共的静态方法, 用来返回这个类的唯一实例

六种 Java 实现

#代码量懒加载线程安全可靠程度说明
1饿汉式(Eager)★★★类加载即实例化,简单直观,无法延迟加载。
2懒汉式(Lazy-非线程安全)只示范学习,生产别用。
3synchronized 方法★★简单但性能差,锁整个方法。
4DCL + volatile★★★双重检查锁 (Double-Checked Locking);JDK 5+ 才完全安全。
5静态内部类★★★★JVM 类加载天生线程安全,推荐。
6枚举单例★★★★★反序列化 / 反射天然防护,极简,强烈推荐

饿汉式

public final class EagerSingleton {private static final EagerSingleton INSTANCE = new EagerSingleton();private EagerSingleton() {}public static EagerSingleton getInstance() { return INSTANCE; }
}
  • 优点:实现最简单、天然线程安全

Java 的语义包证了在引用这个字段之前并不会初始化它, 并且访问这个字段的任何线程都将看到初始化这个字段所产生的所有写入操作.

  • 缺点:类加载就占用内存;若实例创建开销大或很少用到,会浪费。

为什么饿汉式中类加载占内存?

步骤发生位置说明
① 类加载(Loading)ClassLoader把 .class 字节流读进内存,创建 Class 对象,本身几乎不耗多少内存。
② 链接 → 初始化JVM 执行 链接(验证、准备、解析)后,进入 初始化 阶段所有 static 字段<clinit> 方法里按源代码顺序赋值。
饿汉式把单例对象定义成 private static final XXX INSTANCE = new XXX();
—— 这一行在 初始化阶段立即 new 对象
③ 对象驻留Java 堆无论业务代码是否真的使用过 getInstance(),对象已被创建并常驻堆中,直到类被卸载或进程结束。
  • “占内存”指的是 单例实例 已经分配在堆里,而不是 Class 元数据。
  • 若实例本身很大(例如预加载 MB 级的配置或字典),但应用启动后很久才用到,就属于“浪费”。
  • 饿汉式是典型的以空间换时间思想的实现: 不用判断就直接创建, 但创建之后如果不使用这个实例, 就造成了空间的浪费. 虽然只是一个类实例, 但如果是体积比较大的类, 这样的消耗也不容忽视.

懒汉式

线程不安全

public final class LazySingletonUnsafe {private static LazySingletonUnsafe instance;private LazySingletonUnsafe() {}public static LazySingletonUnsafe getInstance() {if (instance == null) {                      // ①instance = new LazySingletonUnsafe();    // ②}return instance;}
}
  • 优点:节省空间, 用到的时候再创建实例对象

为什么多线程下会出问题?

竞态点:假设 T1 与 T2 同时进入方法,instance 仍为 null

  • T1 通过检查(①)进入 ②,开始执行 new,尚未完成。
  • T2 同样看到 instance == null,也执行 new
  • 结果:生成两个实例,违背单例约束

问题原因:没有同步手段(锁、volatile + CAS 等)来保证检查与创建的 原子性

测试案例:

final class LazySingleton {private static LazySingleton instance = null;private LazySingleton() {}public static LazySingleton getInstance() {if (instance == null) {instance = new LazySingleton();}return instance;}
}public class demo {public static void main(String[] args) {Set<String> instanceSet = Collections.synchronizedSet(new HashSet<>());for (int i = 0; i < 1000; i++) {new Thread(() -> {instanceSet.add(LazySingleton.getInstance().toString());}).start();}for (String instance : instanceSet) {System.out.println(instance);}}
}

输出结果:

如果输出的结果中有 2 个或 2 个以上的对象, 就足以说明在并发访问的过程中出现了线程安全问题

LazySingleton@668916a0
LazySingleton@c23df88

synchronized 方法

这样的做法对所有线程的访问都会进行同步操作, 有很严重的性能问题

public final class SynchronizedSingleton {private static SynchronizedSingleton instance;private SynchronizedSingleton() {}public static synchronized SynchronizedSingleton getInstance() {if (instance == null) {instance = new SynchronizedSingleton();}return instance;}
}

双重检查锁 Double Check Lock + Volatile

public final class DCLSingleton {private static volatile DCLSingleton instance;private DCLSingleton() {}public static DCLSingleton getInstance() {// 先判断实例是否存在if (instance == null) {// 加锁创建实例synchronized (DCLSingleton.class) {// 再次判断, 因为可能出现某个线程拿了锁之后, 还没来得及执行初始化就释放了锁,// 而此时其他的线程拿到了锁又执行到此处 ==> 这些线程都会创建一个实例, 从而创建多个实例对象if (instance == null) {instance = new DCLSingleton();}}}return instance;}
}
  • 为什么要双重检查
  • volatile 关键词有什么作用

使用双重检查的原因:

  • 第一次检查:绝大部分时间单例已存在,快速返回,避免进入 synchronized,性能开销 ≈0
  • 第二次检查:只有在第一次判断为 null、并且当前线程拿到锁时才进入;此时仍需再判一次,防止 “T1 创建 →T2 等锁 →T2 再创建” 的并发漏洞。(就是刚才懒汉式导致的问题)

volatile 关键词:

  • 可见性,保证线程中对这个变量所做的任何写入操作对其他线程都是即时可见的,写入 instance 对所有线程立刻可见
  • 禁止 JVM 指令重排:对象创建过程实际上分三步(并不是原子性操作)
a. 分配内存,在堆内存中, 为新的实例开辟空间
b. 调用构造器初始化
c. 将引用赋给变量 (instance = address)

CPU 和编译器可能把 b、c 重排成 c→b。可能发生以下情况:

  • T1 执行到 c,引用已非 null,但对象尚未初始化;
  • T2 读取到“非 null”便返回,使用到的是 半成品对象

volatile 在 Java 内存模型中加了写后读屏障,保证初始化完成先于赋值,彻底避免重排风险。

静态内部类 Singleton Holder

只有在第一次调用 getInstance() 时才被加载,JVM 类加载保证线程安全 ➜ 懒加载 + 免锁

public final class HolderSingleton {private HolderSingleton() {}private static class Holder {private static final HolderSingleton INSTANCE = new HolderSingleton();}public static HolderSingleton getInstance() {return Holder.INSTANCE;}
}

JVM 记载类的时候有以下步骤:① 加载 -> ② 验证 -> ③ 准备 -> ④ 解析 -> ⑤ 初始化

JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类(SingletonHolder)的属性/方法被调用时才会被加载, 并初始化其静态属性(instance)

为什么会将创建实例放在静态内部?

核心机制:Initialization-on-demand holder idiom

  • 懒加载
    • 外部类 HolderSingleton 被加载时,并不会立即加载 Holder
    • 只有首次调用 getInstance(),JVM 才会解析对 Holder.INSTANCE 的主动使用,从而 加载并初始化 Holder,此时才 new 实例
  • 线程安全(不需要锁)
    • JVM 对 类初始化 有“互斥保证”:同一个类的 <clinit> 在多线程环境中只会执行一次,且执行期间其他线程会被阻塞。
    • 实例创建天然是原子且线程安全的
  • 零额外开销
    • 不需要 synchronized、不需要 volatile;除第一次触发外,没有任何同步损耗

具体原因:静态内部类利用了 类的按需加载 + 初始化互斥,同时满足“懒加载 + 线程安全 + 高性能”

枚举单例

public enum EnumSingleton {INSTANCE;// 可添加字段/方法private final Map<String, String> cache = new ConcurrentHashMap<>();public void put(String k, String v) { cache.put(k, v); }public String get(String k) { return cache.get(k); }
}
  • JVM 保证 序列化安全:枚举反序列化时不会新建实例。
  • 防反射:任何试图通过 Constructor.newInstance 创建都会抛 IllegalArgumentException
  • 代码最少,可自然支持 switch

单例运用场景

框架/库场景实现方式
JDKjava.lang.Runtime饿汉式
Log4j / LogbackLoggerContext懒加载 + 双检锁
Spring默认 Bean Scope = singleton容器级单例,非 GoF 模式
MyBatisSqlSessionFactoryBuilder ➜ SqlSessionFactory通常一个全局实例
HikariCPHikariPool 内部维护线程安全单例连接池

破解单例模式

  • 除枚举方式外, 其他方法都会通过反射的方式破坏单例,因此可以在构造方法中进行判断 —— 若已有实例, 则阻止生成新的实例
private Singleton() throws Exception {if (instance != null) {throw new Exception("Singleton already initialized, 此类是单例类, 不允许生成新对象, 请通过getInstance()获取本类对象");}
}
  • 如果单例类实现了序列化接口 Serializable, 就可以通过反序列化破坏单例,因此可以不实现序列化接口, 或者重写反序列化方法 readResolve()
// 反序列化时直接返回当前实例
public Object readResolve() {return instance;
}
  • Object#clone() 方法也会破坏单例, 即使你没有实现 Cloneable 接口 —— 因为 clone()方法是 Object 类中的,需要重写方法并抛出异常

参考

  • 设计模式 - Java 中单例模式的 6 种写法及优缺点对比 - 瘦风 - 博客园

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

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

相关文章

音乐抢单源码(连单卡单/叠加组规则/打针/多语言)

简介&#xff1a; 测试环境&#xff1a;Nginx、PHP7.2、MySQL5.6&#xff0c;运行目录设置为public&#xff0c;伪静态thinkphp&#xff0c;建议开启SSL 测试语言&#xff1a;11种 不知道谁给我的一套&#xff0c;说是买来的&#xff0c;我看了一下功能感觉也一般&#…

分类树查询性能优化:从 2 秒到 0.1 秒的技术蜕变之路

在电商系统中&#xff0c;分类树查询是一个基础且高频的功能&#xff0c;然而这个看似简单的功能背后却隐藏着不小的性能挑战。本文将分享我们在实际项目中对分类树查询功能进行五次优化的全过程&#xff0c;看如何将查询耗时从 2 秒缩短至 0.1 秒&#xff0c;为用户提供更流畅…

Ansible 介绍及安装

简介 Ansible 是一款开源的自动化工具&#xff0c;广泛应用于配置管理、应用部署、任务自动化以及多节点管理等领域。它由 Michael DeHaan 于 2012 年创建&#xff0c;ansible 目前已经已经被红帽官方收购&#xff0c;是自动化运维工具中大家认可度最高的&#xff0c;并且上手…

超光谱相机的原理和应用场景

超光谱相机是光谱成像技术的尖端形态&#xff0c;具备亚纳米级光谱分辨率与超千波段连续覆盖能力&#xff0c;通过“图谱合一”的三维数据立方体实现物质的精准识别与分析。其核心技术架构、应用场景及发展趋势如下&#xff1a;一、核心技术原理1、‌分光机制‌‌干涉分光‌&am…

掌握MySQL函数:高效数据处理指南

​ 在 MySQL 数据库管理系统中&#xff0c;函数扮演着极为重要的角色。它们就像是数据库操作的得力助手&#xff0c;能够帮助开发者高效地完成各种数据处理任务。本文将深入探讨 MySQL 函数的方方面面&#xff0c;从其基本概念到实际应用&#xff0c;帮助读者全面掌握这一强大的…

10.SpringBoot的统一异常处理详解

文章目录1. 异常处理基础概念1.1 什么是异常处理1.2 为什么需要统一异常处理1.3 Spring异常处理机制2. SpringBoot默认异常处理2.1 默认错误页面2.2 自定义错误页面3. 全局异常处理器3.1 基础全局异常处理器3.2 统一响应格式3.3 使用统一响应格式的异常处理器4. 自定义异常4.1 …

No Hack No CTF 2025Web部分个人WP

No Hack No CTF 2025 Next Song is 春日影 hint&#xff1a;NextJS Vulnerability at /adminCVE-2025-29927Next.js 中间件权限绕过漏洞 访问admin路由发现跳转利用CVE&#xff1a; curl -i \-H "x-middleware-subrequest: middleware:middleware:middleware:middleware:m…

STM32第十八天 ESP8266-01S和电脑实现串口通信

一&#xff1a; ESP和电脑实现串口通信1. 配置 WiFi 模式 ATCWMODE3 // softAPstation mode 响应 : OK 2. 连接路路由器器 ATCWJAP"SSID","password" // SSID and password of router 响应 : OK 3. 查询 ESP8266 设备的 IP 地址 ATCIFSR 响应 : CIFSR:APIP…

STM32第十七天ESP8266-01Swifi模块

ESP8266-01S wifi模块1&#xff1a;ESP8266是实现wifi通讯的一个模块种类&#xff0c;有很多分类包含esp8266-12、esp8266-12E、ESP8266-01S、esp32等等。esp8266-01S由一颗esp8266作为主控再由一块flash作为存储芯片组成&#xff0c;带有板载芯片供电采用3.3V电压使用串口进行…

ProCCD复古相机:捕捉复古瞬间

在数字摄影盛行的今天&#xff0c;复古胶片相机的独特质感和怀旧风格依然吸引着众多摄影爱好者。ProCCD复古相机APP正是这样一款能够满足用户对复古摄影需求的应用程序。它通过模拟复古CCD数码相机的效果&#xff0c;让用户在手机上也能轻松拍出具有千禧年风格的照片和视频。无…

Spring Boot 应用启动时,端口 8080 已被其他进程占用,怎么办

1、修改application.yml配置文件&#xff0c;将端口号更改为未被占用的端口&#xff08;例如9090&#xff09;2、以管理员身份运行命令提示符在命令提示符窗口中输入命令netstat -ano | findstr :8080”输出结果可能如下&#xff1a;“TCP 0.0.0.0:8080 0.0.0.0:0 LISTENING xx…

使用Jenkins完成springboot项目快速更新

✨重磅&#xff01;盹猫的个人小站正式上线啦&#xff5e;诚邀各位技术大佬前来探秘&#xff01;✨ 这里有&#xff1a; 硬核技术干货&#xff1a;编程技巧、开发经验、踩坑指南&#xff0c;带你解锁技术新姿势&#xff01;趣味开发日常&#xff1a;代码背后的脑洞故事、工具…

HDLBits刷题笔记和一些拓展知识(九)

文章目录HDLBits刷题笔记CircuitsFsm1Fsm1sFsm2Fsm3onehotExams/ece241 2013 q4Lemmings1Lemmings2Lemmings3Lemmings4Fsm onehotFsm ps2Fsm ps2dataFsm serialFsm serialdataFsm serialdpFsm hdlc未完待续HDLBits刷题笔记 以下是在做HDLBits时的一些刷题笔记&#xff0c;截取一…

CD46.【C++ Dev】list的模拟实现(1)

目录 1.STL库的list 2.模拟实现 节点结构体 list类 无参构造函数 尾插函数 迭代器★ begin() operator 前置 后置 operator-- 前置-- 后置-- operator! operator end() operator* const修饰的迭代器的设计 1.STL库的list 模拟实现list之前,先看看STL库里的…

数据结构——二叉树的基本介绍

————————————本文旨在讨论与学习计算机知识&#xff0c;欢迎交流————————————上一章&#xff0c;我们讲解了树结构的综述导论&#xff0c;那么&#xff0c;现在我们来深入了解一下树结构中最常用研究的结构——二叉树结构&#xff08;上一章的扩展——…

英伟达发布 Llama Nemotron Nano 4B:专为边缘 AI 和科研任务优化的高效开源推理模型

英伟达推出了 Llama Nem)otron Nano 4B&#xff0c;这是一款专为在科学任务、编程、符号运算、函数调用和指令执行方面提供强大性能与效率而设计的开源推理模型&#xff0c;其紧凑程度足以支持边缘部署。该模型仅包含 40 亿参数&#xff0c;却在内部基准测试中实现了比其他多达…

论文阅读笔记——Autoregressive Image Generation without Vector Quantization

MAR 论文 基于 VQ&#xff08;向量量化&#xff09;的图像生成方法具有显著优势&#xff0c;它通过离散化压缩将原始图像映射到有限的 codebook 空间&#xff0c;从而缩小学习范围、降低建模难度&#xff0c;同时这种离散表示更易于与自回归&#xff08;AG&#xff09;生成方式…

【科普】关于C 语言日志系统实战:如何同时输出到终端和文件?

1.概述 c语言没有现成的日志库&#xff0c;如果要记录日志&#xff0c;需要自己封装一个日志库。如果要实现日志级别和参数打印&#xff0c;还是比较麻烦的&#xff0c;正好在github找到了一个c语言开源日志库&#xff0c;可以实现日志级别打印&#xff0c;参数打印&#xff0…

2025,数字人借直播场景迈过“真假线”丨数智化观察

作者 | 曾响铃文 | 响铃说一夜带货超5500万GMV、观看人次1300万&#xff0c;罗永浩数字人在百度电商的直播首秀正在掀起新的行业浪潮——2025&#xff0c;数字人直播带货成功出圈&#xff0c;加速进入大众视野&#xff0c;被更多的消费者所认可。成就这场热潮的关键点之一&…

HTML表格导出为Excel文件的实现方案

1、前端javascript可通过mime类型、blob对象或专业库&#xff08;如sheetjs&#xff09;实现html表格导出excel&#xff0c;适用于中小型数据量&#xff1b;2、服务器端方案利用后端语言&#xff08;如python的openpyxl、java的apache poi&#xff09;处理复杂报表和大数据&…