Spring 单例 Bean 是线程安全的吗?

一、面试标准答案(30 秒杀手锏

"分情况看

**如果 Bean 是无状态的(如 Service/DAO),**Spring 不会做任何多线程封装,由开发者自行保证线程安全 ------但因为这些 Bean 没有可变状态(成员变量),实际上是线程安全的

如果 Bean 有可变状态(如 View Model/POJO 携带成员变量),开发者必须自行处理同步,Spring 不管。最常见的做法是把 scope 改成 prototype,让每次注入都是新实例。"

二、为什么 Spring 单例 Bean 默认是"实际线程安全"

2.1 三种情况分析
情况 是否线程安全 原因
无状态 Bean(Service/DAO) ✅ 实际安全 没有成员变量 / 成员变量是 final / 方法参数都是局部变量
有状态 Bean 但无成员变量修改 ✅ 实际安全 状态都在方法参数 / 局部变量里(线程栈隔离)
有状态 Bean 且修改成员变量 不安全 多线程共享同一个实例,成员变量被并发改
2.2 为什么 Spring 不封装多线程?
原因 解释
1. 性能 加锁/synchronized 会让 Bean 性能降 10-100 倍
2. 与 Spring 设计哲学冲突 Spring 是"轻量级容器",不做重量级封装
3. 无意义 Service/DAO 本来就无状态,封装没意义
4. 复杂业务 有状态 Bean 的同步策略因业务而异,Spring 不知道该用哪种
2.3 Spring 的官方说法

"Spring 框架没有对单例 bean 进行任何多线程的封装处理。关于单例 bean 的线程安全和并发问题需要开发者自行去搞定。"

三、Java 内存模型(JMM)核心知识(面试加分

3.1 线程栈 vs 堆

线程栈(私有) 堆(共享)

┌──────────────┐ ┌──────────────┐

│ 局部变量 │ │ 对象实例 │

│ 方法参数 │ │ 成员变量 │

│ 临时计算结果 │ │ 静态变量 │

└──────────────┘ └──────────────┘

↓ ↓

每个线程一份 所有线程共享

关键点:

  • 局部变量 / 方法参数 = 线程私有(每个线程一份,天然安全)
  • 成员变量 / 静态变量 = 线程共享 (多线程会并发修改,危险
3.2 无状态 Bean 为什么安全?

@Service // 单例

public class UserService {

// 没有成员变量!所有数据都在方法参数里

public User findById(Long id) { // id 是参数,线程私有

return userDao.findById(id);

}

public void save(User user) { // user 是参数,线程私有

userDao.save(user);

}

}

为什么安全?

  • 1000 个线程同时调用 findById(123L)
  • 每个线程有自己独立的 id 参数
  • 没有共享变量被修改
  • 天然线程安全

四、有状态 Bean 的 4 种处理方案

方案 1:scope = prototype(最常用

@Service

@Scope("prototype") // 每次注入都是新实例

public class ShoppingCart {

private List<Item> items = new ArrayList<>(); // 成员变量(可变)

public void addItem(Item item) {

items.add(item);

}

}

缺点: 每次注入新对象,失去 Spring 单例的性能优势(创建/GC 成本高)。

方案 2:ThreadLocal(金融项目实战用得多

@Service

public class UserContext {

// 每个线程独立一份,不互相干扰

private static final ThreadLocal<User> currentUser = new ThreadLocal<>();

public static void set(User user) {

currentUser.set(user);

}

public static User get() {

return currentUser.get();

}

public static void clear() {

currentUser.remove(); // 必须清理!防止内存泄漏

}

}

金融项目实战: Spring Security 的 SecurityContextHolder、日志 MDC(traceId 追踪)都是 ThreadLocal 实现的。

方案 3:加锁 / synchronized(金融项目最后手段

@Service

public class CounterService {

private int count = 0; // 共享变量

public synchronized void increment() { // 锁

count++;

}

}

缺点: 性能差,不推荐

方案 4:使用 ConcurrentHashMap / AtomicInteger推荐

@Service

public class CounterService {

private final AtomicInteger count = new AtomicInteger(0); // CAS 无锁

public void increment() {

count.incrementAndGet(); // 原子操作,无需加锁

}

}

优点: 性能高 + 线程安全,金融项目首选(计数器、累加器、统计指标)。

五、面试官追问应对

追问 1:Spring Bean 默认是单例还是多例?

"默认 singleton(单例),容器启动时创建一次,整个应用共享。

还有 5 种 scope:prototype(多例)/ request(一次请求一个)/ session(一次会话一个)/ application(一次应用一个)/ websocket(一次 WebSocket 一个)。"

追问 2:单例 Bean 怎么变成多例?

"单例 Bean 变多例有 4 种写法

1.简单场景@Scope("prototype")

2.类型安全@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

3.工厂方法@Bean + @Scope

4.注入到单例要新实例@Scope + proxyMode = TARGET_CLASS@Lazy

关键坑:prototype 注入到 singleton,默认不是真多例,必须用 proxyMode 或 @Lazy。"

追问 3:Spring 怎么处理有状态 Bean?

"不处理 。Spring 把多线程安全问题完全交给开发者。如果 Bean 有可变成员变量,开发者需要:

1.用 ThreadLocal 隔离

2.用 synchronized / ReentrantLock 同步

3.用 AtomicInteger / ConcurrentHashMap 无锁并发

4.改 scope = prototype

报表计数用 AtomicInteger,日志 traceId 用 ThreadLocal,效果都很好。"

追问 4:Controller 是单例吗?会有线程安全问题吗?

"Controller 默认单例 。但因为 Controller 是无状态的(成员变量基本是 Service 引用,Service 本身也是单例无状态),实际线程安全

如果你在 Controller 里直接用成员变量存请求数据 (如 private User currentUser),会线程不安全------多个请求会覆盖。

正确做法:参数传递,或用 ThreadLocal 存当前请求的用户信息。"

追问 5:Spring 的 @Transactional 事务方法线程安全吗?

"不保证。Spring 事务是用 ThreadLocal 存 Connection 的(保证同一个事务用同一个连接)。

但 @Transactional 方法本身不是线程安全保证 。如果在事务方法里修改成员变量,照样不安全。事务只保证 ACID,不保证并发安全。"

六、一句话总结

"Spring 单例 Bean 默认线程安全,前提是无状态。Service/DAO 没有成员变量,每个线程有自己的方法参数(线程栈隔离),所以安全。

有状态 Bean (如 View Model 带成员变量)不安全,要用 ThreadLocal / AtomicXxx / synchronized / scope=prototype 处理。

Spring 不会帮你处理多线程,由开发者自行保证。"

七、记忆口诀

"Spring 单例 + 无状态 = 安全,Spring 单例 + 有状态 = 不安全"

"Service/DAO 不用管,View Model 自己管"

"ThreadLocal 隔离,AtomicXxx 计数,synchronized 兜底,prototype 多例"

"局部变量是线程私有的,成员变量是线程共享的"

相关推荐
2601_951643772 小时前
Python第一,Java跌出前三,C语言杀回来了
java·c语言·python·编程语言排行·技术趋势
小小编程路3 小时前
C++ 异常 完整讲解
开发语言·c++
AI科技星3 小时前
数术工坊 · 第四卷 橡皮泥江湖(拓扑学)【完整定稿】
c语言·开发语言·汇编·electron·概率论·拓扑学
张忠琳3 小时前
【Go 1.26.4】Golang Select 深度解析
开发语言·后端·golang
IT 行者5 小时前
GitHub Spec Kit 实战(五):/speckit.tasks 怎么拆——Spec Kit 五部曲收官
java·ai编程·claude
AC赳赳老秦5 小时前
OpenClaw+Power Apps 实战:自动生成 Power Apps 应用、连接 Excel 数据源
大数据·开发语言·python·serverless·excel·deepseek·openclaw
提笔了无痕5 小时前
如何用Go实现整套RAG流程
开发语言·后端·golang
(Charon)5 小时前
【C++ 面试高频基础:指针、引用、const、static、new/delete 总结】
java·开发语言
Yeats_Liao5 小时前
Feed流系统设计(三):数据模型与存储设计,从表结构到Redis收件箱
java·javascript·redis