Spring 中的多线程与高并发实战(初/中级)
这一篇不讲 JVM 细节,重点是:在 Spring/SpringBoot 项目里,怎么“正确地”用多线程和保护接口,扛住高并发。
一、面试官常问的几类问题
- 你们项目里有用到多线程吗?是怎么用的,用的是什么线程池?
@Async是怎么用的?线程池在哪里配置?怎么排查线程池打满的问题?- 高并发请求打到一个接口上时,你们有什么限流/降级措施?
- 订单、库存这类接口,怎么避免重复请求、超卖?
下面用几个常见场景,把这些问题串起来。
二、在 Spring 里“正确”地用线程池和 @Async
1. 不要在 Controller 里随手 new Thread()
- 在 Web 项目里,最常见的错误就是:java
// ❌ 反例:Controller 里直接创建线程 @PostMapping("/order") public String createOrder() { new Thread(() -> doCreateOrder()).start(); return "OK"; } - 问题:
- 线程没办法统一管理(数量不可控、OOM 风险)。
- 无法方便地打日志、监控、设置名字。
面试可以直接说一句:所有线程资源都应该交给 Spring 管理,通过线程池统一配置和监控。
2. 使用 @Async + 线程池
基本步骤:
开启异步支持:
java@EnableAsync @SpringBootApplication public class Application { }配置一个线程池 Bean:
java@Configuration public class AsyncConfig { @Bean("commonExecutor") public ThreadPoolTaskExecutor commonExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(4); executor.setMaxPoolSize(8); executor.setQueueCapacity(100); executor.setThreadNamePrefix("async-"); executor.initialize(); return executor; } }在 Service 上使用
@Async:java@Service public class NotifyService { @Async("commonExecutor") public void sendEmail(String userId) { // 发送邮件的耗时操作 } }
面试时可以简单描述:“我们项目里所有异步任务都是用
@Async + ThreadPoolTaskExecutor,线程池参数统一配置,可监控、可限流。”
三、高并发接口的保护:限流 + 超时 + 降级
高并发场景下,面试官常问:“如果突然有很多请求打到一个接口上,你们怎么防止系统被打挂?”
可以从三点去回答:限流、超时、降级。
1. 简单限流思路(基于拦截器 + Redis)
- 在网关或 SpringMVC 拦截器里,对某个接口做 QPS 限制:
- 例如:每个用户/每个 IP,每分钟最多调用 N 次。
- 可以在 Redis 里以
key = userId:接口名方式计数,超过阈值直接返回友好提示。
示意代码(伪代码):
java
String key = userId + ":order:create";
Long count = redis.incr(key);
redis.expire(key, 60); // 60 秒窗口
if (count > 100) {
throw new BizException("请求过于频繁,请稍后再试");
}面试时不需要写很完整的代码,重点讲清楚“在拦截器/网关层做限流,落地可以用 Redis 计数或接入 Sentinel/Resilience4j 这类组件”。
2. 接口调用链的超时和降级
- 外部接口(如三方支付、短信服务):
- 在
RestTemplate/ WebClient / OpenFeign 上设置连接超时、读取超时。 - 超过阈值快速返回,并记录日志,避免线程长时间挂起。
- 在
- 可以配合熔断/降级:
- 比如短信服务挂了,先记录日志 + 落库,后面用定时任务补发,而不是让用户页面一直转圈。
一句话总结:“宁可早点失败,也不要让线程一直卡住,把整个线程池拖垮。”
四、高并发下的典型业务问题:重复下单、库存超卖
这一块和数据库/Redis 有一定关系,但经常会在 “Spring + 高并发” 场景一块被问到,可以准备一个通用的回答思路。
1. 重复下单(接口被多次点击 / 重复请求)
常见几种处理方式:
- 前端防重复点击:按钮置灰、节流,但只能作为第一层防线。
- 后端幂等设计:
- 生成一个业务幂等号(如订单提交 token),一次请求用一次。
- 在 Redis 里
SETNX保存,如果已存在说明是重复请求,直接返回上次结果或提示。
- 数据库唯一约束:
- 例如订单表里
(user_id, activity_id)建唯一索引,从根上防止“一人多单”。
- 例如订单表里
2. 库存超卖(秒杀场景)
可以简单提到两层思路:
- 减库存操作必须放在事务里,搭配行锁/版本号,避免并发修改同一行导致脏数据。
- 高并发场景可以先用 Redis 预减库存 + MQ 异步落库,减少数据库压力(这一块可以略讲,不深入细节)。
面试时,重点是给出“我们不是只靠前端,而是从 Redis + 数据库约束两层防御”这样的思路。
五、总结:怎么把这些讲成一个完整的“高并发”回答?
可以准备一个统一的说法,例如:
- 我们项目中高并发相关主要做了几件事:
- 多线程:所有异步任务统一使用
@Async + ThreadPoolTaskExecutor,线程池参数集中配置,方便监控和调优。 - 接口保护:在网关/拦截器层做简单限流(基于 Redis 计数),同时为外部接口配置合理的超时时间,避免线程池被占满。
- 业务幂等与防超卖:订单/库存等敏感接口通过幂等号 + 数据库唯一约束 + 合理的事务/锁机制,避免重复下单和超卖。
- 多线程:所有异步任务统一使用
- 这样既能展示你懂一些多线程基础,又能体现你在 Spring 框架下具体是怎么落地高并发处理 的。