P64 别再误解了,Spring 的单例是容器视角,不是 JVM 视角
面试题:Spring 的单例 Bean 和"单例模式"是同一个概念吗?
1. 核心结论
不是一回事。 Spring 的单例是容器视角:同一个 Spring 容器内,该 Bean 只有一个实例;但同一个 JVM 里可以有多个 Spring 容器,也可以有多个该类的实例。
2. 对比
| 对比项 | 单例模式(GoF) | Spring 单例 Bean |
|---|---|---|
| 作用范围 | 整个 JVM 一个实例 | 每个容器一个实例 |
| 实现方式 | 私有构造器 + 静态方法 | 容器管理,默认 scope=singleton |
| 多个实例可能 | 不可能(构造器私有) | 可能(多个容器/多份 classloader) |
| 控制权 | 类自己控制 | 容器控制生命周期 |
3. 为什么说"容器视角"
java
AnnotationConfigApplicationContext ctx1 = new AnnotationConfigApplicationContext(AppConfig.class);
AnnotationConfigApplicationContext ctx2 = new AnnotationConfigApplicationContext(AppConfig.class);
ctx1.getBean(UserService.class) == ctx1.getBean(UserService.class); // true,同一容器内单例
ctx1.getBean(UserService.class) == ctx2.getBean(UserService.class); // false,不同容器不同实例另外:
- 一个 JVM 多个 Spring 容器很常见:Spring Boot 主容器 + 测试容器 + 插件/模块容器;
- 多个 classloader(如 Tomcat 多个 Web 应用)各加载一份类,即使都叫 singleton 也不是同一个对象;
- 集群部署时,每个 JVM 各有一个容器 → 每个 JVM 各有一份 Bean 实例。
4. 影响与注意点
正因为是"每容器单例":
- Bean 里的状态(成员变量)是容器内共享的,多线程要小心线程安全(如无状态 Service 最安全);
- 别假设"全局唯一":跨容器拿不到对方的 Bean;
- 分布式/多实例部署下,Bean 内的本地状态不共享,需要外部存储。
5. 加分点
- 面试官追问"那 Spring 单例 Bean 线程安全吗":单例 ≠ 线程安全,取决于 Bean 是否有可变状态;
- 主动提到 scope 全家桶:singleton / prototype / request / session / application;
- 提到"Spring 单例缓存":
DefaultSingletonBeanRegistry的单例池(三级缓存第一级)。
一句话总结
Spring 单例是"每个容器一个实例",不是"每个 JVM 一个实例";同一个 JVM 里多个容器/多个 classloader 可以有多个实例,集群下更是每台机器各一份——所以别把容器单例当全局单例用。