从 JDK 到 Spring,单例模式在源码中的实战用法

上一篇文章从面试高频到实战落地:单例模式全解析(含 6 种实现 + 避坑指南)聊了单例模式的 6 种基础实现,后台有不少同学说:"理论看了就忘,平时根本用不上啊!"

其实不然,今天我试着给出几个我知道的常用框架中的单例模式,也期待大家可以在工作中多多观察

一、 java.lang.Runtime

打开 IDE 搜Runtime,你会发现这是一个完美的饿汉式单例,也是 JVM 运行时环境的核心入口类😃

java 复制代码
public class Runtime {
    private static final Runtime CURRENT_RUNTIME = new Runtime();

    public static Runtime getRuntime() {
        return CURRENT_RUNTIME;
    }

    private Runtime() {} // 私有构造函数
}

那么为什么Runtime会选择饿汉式呢

  • JVM类加载时初始化唯一实例,天然线程安全
  • Runtime作为JVM运行时环境的入口,必须在应用启动后立即可用(Runtime 负责管理 JVM 资源(如内存、进程),应用启动后随时可能调用exec()执行系统命令、totalMemory()获取内存,延迟加载会导致功能缺失。
  • Runtime仅维护JVM状态信息,内存占用极小,提前初始化不会造成资源浪费。

那为什么不用其他的构造方式?

  • 懒汉式/DCL:启动后就要使用,没有必要多此一举去判断
  • 静态内部类:虽支持延迟加载,但Runtime 属于必需品,反而增加代码复杂度。

总结:时间紧任务重,直接饿汉简单快捷.

二、HikariDataSource(高性能数据库连接池,SpringBoot默认连接池)

💡这里就不做HiKariCP和Druid哪个好用的对比了🐶

java 复制代码
public class HikariDataSource extends HikariConfig implements DataSource, Closeable {
    private volatile HikariPool pool; // 关键:volatile修饰连接池实例

    @Override
    public Connection getConnection() throws SQLException {
        if (pool == null) {
            synchronized (this) { // 双重检查锁保证线程安全
                if (pool == null) {
                    pool = new HikariPool(this); // 初始化连接池
                }
            }
        }
        return pool.getConnection();
    }
}

选择逻辑

  • 高频调用场景 :getConnection()可以说是我们日常工作中最频繁的事件了,需要在保证线程安全的前提下尽可能保证效率(创建实例后性能约等于无锁)。
  • 延迟加载必要性:连接池初始化需要加载配置、创建连接池(可能几十上百个连接),如果应用启动就初始化,会拖慢启动速度;如果某些功能用不到数据库,更是纯纯浪费内存。

对比排除:

  • 饿汉式: 启动慢 + 内存浪费(尤其多数据源场景)
  • 静态内部类: 无法传递配置参数,不符合连接池动态初始化需求
  • 枚举: 参数固定死,无法修改连接池配置(如最大连接数)

总结:对于重量级、调用频率波动大、需动态配置的组件,DCL 是工业级首选。

三、Spring.PathMatchingResourcePatternResolver (Spring 的资源解析器)

java 复制代码
public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {
	private static class PatternVirtualFileVisitor {
		        // 资源扫描的核心逻辑
        public void visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            // ... 扫描文件并处理
        }
    }

    // 获取资源时才创建内部类实例
    public Resource[] getResources(String locationPattern) throws IOException {
        PatternVirtualFileVisitor visitor = new PatternVirtualFileVisitor();
        // ... 执行扫描逻辑
        return resources.toArray(new Resource[0]);
    }
	}
}

选择逻辑

  • 初始化成本高:资源扫描需要遍历类路径文件,耗时较长,延迟加载能避免启动时的性能损耗,同时框架实现需要尽量优雅。
  • 线程安全零代码 :依赖 JVM 类加载机制保证线程安全,不用写synchronizedvolatile,源码更优雅🐶;

对比排除

  • DCL:需额外volatile关键字,且Spring源码追求简洁性。
  • 枚举:资源解析器需继承ResourcePatternResolver接口,而枚举无法继承类/接口。

总结:对于可有可无但是初始化成本高的场景可以考虑这种实现,无需显式同步。

四、HystrixCommandGroupKey(Hystrix的分组key管理器 🔧)

java 复制代码
public interface HystrixCommandGroupKey {

    String name();

    class Factory {
        //用一个静态的map来存储已经创建过的HystrixCommandKey
        private static ConcurrentHashMap<String, HystrixCommandGroupKey> intern = new ConcurrentHashMap<>();
        public static HystrixCommandGroupKey asKey(String name) {
            HystrixCommandGroupKey key = intern.get(name);
            if (key == null) {
                key = new DefaultHystrixCommandGroupKey(name);
                intern.putIfAbsent(name, key);
            }
            return key;
        }
    }
}

选择逻辑:

  • 多项单例工厂管理:因为需要为不同业务组分配不同的策略,需要容器式的管理
  • 并发容器保证安全效率:ConcurrentHashMap的无锁读写和原子化操作可以代替没有必要的安全处理

为什么不用传统单例?

Hystrix 需要为不同业务组(如订单、支付)创建独立的熔断策略,传统单例只能有一个实例,完全满足不了需求。

总结: 当需要按维度隔离单例、灵活管理多实例时,ConcurrentHashMap + 工厂模式是最优解!

最后

设计模式需要灵活运用------最好的实现不是最优雅的代码,而是最贴合业务场景的取舍

也期待大家在工作中的发现哦(其实是在求评论🥹🥺🥺)

相关推荐
MadPrinter26 分钟前
SpringBoot学习日记 Day11:博客系统核心功能深度开发
java·spring boot·后端·学习·spring·mybatis
dasseinzumtode26 分钟前
nestJS 使用ExcelJS 实现数据的excel导出功能
前端·后端·node.js
淦出一番成就29 分钟前
Java反序列化接收多种格式日期-JsonDeserialize
java·后端
Java中文社群31 分钟前
Hutool被卖半年多了,现状是逆袭还是沉寂?
java·后端
程序员蜗牛1 小时前
9个Spring Boot参数验证高阶技巧,第8,9个代码量直接减半!
后端
yeyong1 小时前
咨询kimi关于设计日志告警功能,还是有启发的
后端
库森学长1 小时前
2025年,你不能错过Spring AI,那个汲取了LangChain灵感的家伙!
后端·openai·ai编程
Java水解2 小时前
Spring Boot 启动流程详解
spring boot·后端
学历真的很重要2 小时前
Claude Code Windows 原生版安装指南
人工智能·windows·后端·语言模型·面试·go
转转技术团队2 小时前
让AI成为你的编程助手:如何高效使用Cursor
后端·cursor