上一篇文章从面试高频到实战落地:单例模式全解析(含 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 类加载机制保证线程安全,不用写
synchronized
或volatile
,源码更优雅🐶;
对比排除:
- 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 + 工厂模式是最优解!
最后
设计模式需要灵活运用------最好的实现不是最优雅的代码,而是最贴合业务场景的取舍。
也期待大家在工作中的发现哦(其实是在求评论🥹🥺🥺)