从 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 + 工厂模式是最优解!

最后

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

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

相关推荐
掉头发的王富贵几秒前
ShardingSphere-JDBC入门教程(上篇)
spring boot·后端·mysql
盖世英雄酱581369 分钟前
必须掌握的【InheritableThreadLocal】
java·后端
LovelyAqaurius11 分钟前
乐观锁及其实现方式详解
后端
绝无仅有13 分钟前
编写 Go 项目的 Dockerfile 文件及生成 Docker 镜像
后端·面试·github
tager23 分钟前
🍪 让你从此告别“Cookie去哪儿了?”
前端·javascript·后端
ERP老兵_冷溪虎山23 分钟前
GoLand 卡成幻灯片?Gopher 必藏的 vmoptions 调优表(续集:WebStorm 飞升后,轮到 Go 开发神器起飞)
后端·go
绝无仅有25 分钟前
使用 Docker 部署 Go 项目(Beego 框架)
后端·面试·github
leonkay26 分钟前
C# 现代化锁的最佳实践
后端
紫穹26 分钟前
002.从0开始,实现第一个deepseek问答
后端
紫穹27 分钟前
004.从 API 裸调到 LangChain
后端