网易首页 > 网易号 > 正文 申请入驻

后端开发最容易踩坑的5大并发场景:原理、故障复现、代码修复与性能调优全指南

0
分享至

后端开发最容易踩坑的5大并发场景:原理、故障复现、代码修复与性能调优全指南
> 摘要:并发问题是后端线上故障的重灾区,很多时候本地测试完全正常,一旦压测、线上高并发就出现数据错乱、超卖、死锁、CPU飙升、内存泄露等诡异问题。本文结合真实生产故障案例,从业务场景出发,讲解数据库锁、线程池、内存可见性、接口并发、缓存击穿五大高频并发难题,附带错误代码、根因分析、修复方案、性能优化策略,所有代码均可直接落地业务,适合后端开发、性能调优、面试复习阅读。
前言
很多后端工程师写业务CRUD得心应手,但是一碰到并发场景就容易翻车。
本地单线程调试,一切逻辑完美;压测一上、流量上来,就出现库存超卖、订单重复创建、数据覆盖丢失、死锁卡死、服务CPU打满等各类难以复现的线上bug。
并发bug最大的特点:无法稳定复现,只在高并发下随机触发,排查难度极高。很多团队靠加`sleep`、加锁瞎改去临时修复,治标不治本,随时可能再次爆发故障。
本文聚焦互联网后端真实业务中最高频的5类并发故障场景,不堆砌空洞理论,每一个场景包含:业务背景、错误复现代码、故障现象、底层原理剖析、正确修复代码、性能优化方案、生产环境避坑要点。
读完本文,你可以识别绝大多数并发隐患,写出健壮的高并发业务代码,同时能够处理线上并发类故障排查。
一、数据库并发:行锁失效导致库存超卖
业务背景
商品库存扣减业务,下单的时候校验库存,库存充足则扣减库存生成订单。这是几乎所有电商、积分兑换系统都会遇到的场景。很多开发者会写出下面这种看上去没问题的代码。
❌错误业务代码
java
// 错误写法:先查询,再扣减,数据库层面无原子保护
@Transactional(rollbackFor = Exception.class)
public boolean deductStock(Long goodsId, Integer num){
//1.查询当前库存
GoodsStock stock = stockMapper.selectById(goodsId);
if(stock == null || stock.getStock() < num){
return false;
}
//2.内存中计算剩余库存
int remain = stock.getStock() - num;
stock.setStock(remain);
stockMapper.updateById(stock);
return true;
}
故障现象
本地单线程测试完全正常。并发压测,大量请求同时进来,出现库存被扣成负数,发生超卖。
原理剖析
这就是典型的读‑写分离并发问题。
1. 多个并发线程同时执行`selectById`,读到同一个库存数值;
2. 所有线程都判断库存充足,在JVM内存做减法;
3. 先后执行update更新数据库,后面的update直接覆盖前面线程修改的数据;
4. 数据库没有感知多线程的修改,最终库存被过度扣减,产生超卖。
很多人以为加上`@Transactional`事务注解就可以解决并发,事务只保证原子性,不解决多线程的竞争问题。事务不会自动加行锁。
✅方案一:数据库悲观锁 select for update
java
@Transactional(rollbackFor = Exception.class)
public boolean deductStock(Long goodsId, Integer num){
// 查询的时候加行级排他锁,锁住这条记录,其他事务阻塞等待锁释放
GoodsStock stock = stockMapper.selectStockForUpdate(goodsId);
if(stock == null || stock.getStock() < num){
return false;
}
int remain = stock.getStock() - num;
stock.setStock(remain);
stockMapper.updateById(stock);
return true;
}
对应的Mapper SQL:
sql
select from goods_stock where id = {goodsId} for update;
> ⚠️重要避坑:`for update`想要生效,查询条件必须命中索引,否则会升级为表锁,整张表被锁住,业务直接卡死。
✅方案二:乐观锁(版本号,高并发推荐)
给数据表增加`version int`版本字段,利用数据库update原子特性做版本校验。
java
@Transactional(rollbackFor = Exception.class)
public boolean deductStock(Long goodsId, Integer num){
GoodsStock stock = stockMapper.selectById(goodsId);
if(stock == null || stock.getStock() < num){
return false;
}
//基于版本号更新,版本不匹配则更新行数为0,表示已经被其他线程修改
int effectRows = stockMapper.updateStockWithVersion(
goodsId,num,stock.getVersion()
);
return effectRows > 0;
}
SQL:
sql
update goods_stock
set stock = stock - {num}, version = version +1
where id = {goodsId} and version = {version};
✅方案三:直接SQL原子扣减(性能最优)
直接在SQL里面完成判断+扣减,完全利用数据库原子能力,不需要在业务代码做查询判断。
sql
update goods_stock
set stock = stock - {num}
where id = {goodsId} and stock >= {num};
生产选型与性能优化
1. 并发压力不大,追求简单稳定:悲观锁 for update;
2. 高并发场景,不希望大量线程阻塞等待:乐观锁版本号,业务做好重试机制;
3. 简单库存扣减,优先直接使用原子SQL更新,代码最少,性能最高;
4. 超超高并发场景,数据库压力过大,引入Redis做库存预扣减,数据库做最终落地。
> 坑点提醒:乐观锁会出现更新失败,业务需要做重试,不能直接返回失败给用户。




二、线程池使用不当引发线上故障
线程池是后端开发高频工具,但是很多人直接使用`Executors`快捷创建线程池,线上经常出现OOM、线程耗尽、服务雪崩。
业务背景
业务需要异步处理消息、处理任务,开发图省事直接使用`Executors.newFixedThreadPool`、`Executors.newCachedThreadPool`。
❌错误代码
java
// 不推荐!Executors工具类创建线程池存在风险
ExecutorService executor = Executors.newCachedThreadPool();
public void handleTask(Runnable task){
executor.submit(task);
}
故障现象
`newCachedThreadPool`最大线程无上限,短时间大量任务过来,疯狂创建线程,线程数量暴涨,CPU上下文切换暴涨,内存占用持续走高,最终OOM宕机。
而`newFixedThreadPool`队列无边界,任务堆积全部放入队列,大量任务对象堆积,内存持续上涨,OOM。
原理剖析
`Executors`封装的线程池:
- `newCachedThreadPool`:最大线程数`Integer.MAX_VALUE`,可以无限创建线程;
- `newFixedThreadPool`:使用无界队列`LinkedBlockingQueue`,任务无限堆积。
阿里开发手册明确强制规定:禁止使用Executors创建线程池,全部手动new ThreadPoolExecutor自定义参数。
✅正确写法,手动定义线程池
java
ThreadPoolExecutor businessThreadPool = new ThreadPoolExecutor(
8, //核心线程数
16, //最大线程数
30L, //空闲线程存活时间
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(200), //有界队列,设置队列最大容量
new ThreadPoolExecutor.CallerRunsPolicy() //拒绝策略:调用者线程执行,不丢弃任务
);
参数怎么配置?生产调优经验
1. CPU密集任务:核心线程数 = CPU核心数 +1;
2. IO密集任务(数据库、RPC、http调用):核心线程可以设置为CPU2~4;
3. 队列一定要设置有界队列,设置合理阈值,拒绝策略必须业务评估;
4. 拒绝策略:
- `CallerRunsPolicy`:任务交给提交任务的线程执行,起到限流效果;
- `AbortPolicy`直接抛异常,业务捕获做告警;
- 不建议DiscardPolicy静默丢弃任务,任务丢失很难发现。
生产额外优化
1. 给线程池自定义线程名称,方便线上日志排查;
2. 增加监控:队列长度、活跃线程数、完成任务数,接入监控告警;
3. 不同业务使用独立线程池,避免一个业务打满线程池影响其他业务。
java
//自定义线程工厂,设置线程名称
ThreadFactory factory = new ThreadFactoryBuilder()
.setNameFormat("business-task-%d")
.build();
> 线上坑:很多人忽视拒绝策略,队列满了直接丢弃任务,业务出现任务无声丢失,故障很难排查。
三、内存可见性问题:多线程变量更新不生效
业务背景
后台开启一个后台轮询线程,用一个boolean变量控制线程退出。
❌错误代码
java
private static boolean stop = false;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while (!stop){
//循环执行业务轮询
}
System.out.println("线程退出");
}).start();
Thread.sleep(1000);
stop = true;
System.out.println("已经发送停止信号");
}
故障现象
主线程修改`stop=true`,但是后台线程永远不会退出,死循环。本地部分机器可以复现,部分机器不能复现。
原理剖析
Java内存模型JMM,CPU缓存优化。后台线程把stop变量读到CPU高速缓存,主线程修改stop只是修改主内存,后台线程看不到最新的值。这就是可见性问题。
✅修复方案:volatile关键字
volatile可以保证变量修改立刻对其他线程可见,禁止CPU缓存优化。
java
private static volatile boolean stop = false;
volatile适用边界(重点,很多人误用)
1. volatile只能保证可见性,不能保证原子性;
2. 适合boolean标记、状态标记;
3. 不适合计数操作 `count++`,count++不是原子操作,多线程下依旧会错乱。
> 误区:很多开发者以为volatile可以解决一切并发问题,用来做计数器,结果线上数据错乱。计数器场景应该使用`AtomicInteger`。
四、接口高并发重复请求,重复下单、重复提交
业务背景
前端网络抖动,用户快速点击提交按钮,用户重复刷新,会产生大量重复请求,后端出现重复创建订单、重复扣款、重复生成数据。
❌问题场景
前端没有做防重,后端接口没有任何幂等保护,同一个请求多次到达服务,产生多条重复业务数据。
故障现象
同一个用户,一次操作生成多条订单记录,重复扣减账户余额。
四种落地解决方案,从简单到复杂
方案1:前端层面拦截(只能做辅助,不能作为唯一防护)
前端按钮点击之后置灰,禁止重复点击。后端必须做校验,前端防护可以被绕过。
方案2:数据库唯一索引(简单业务首选)
业务生成唯一业务流水号,数据库增加唯一索引,重复插入直接抛出唯一索引冲突异常,拦截重复数据。
sql
UNIQUE KEY uk_order_no (`order_no`)
> 优点:简单可靠,数据库层面拦截;缺点:只针对插入场景。
方案3:Token幂等方案(接口通用方案)
1. 业务提交前,后端生成唯一token返回前端;
2. 用户提交请求带上token;
3. Redis校验token,校验成功直接删除token;
4. 重复请求token已经被删除,直接拒绝请求。
伪代码:
java
String token = request.getHeader("idempotent-token");
//Redis删除token,如果返回true代表第一次访问
Boolean ok = redisTemplate.delete(token);
if(Boolean.FALSE.equals(ok)){
return "请勿重复提交";
}
//执行业务逻辑
createOrder();
方案4:业务状态机校验
订单状态是已创建,拒绝再次执行创建逻辑。适合状态流转业务。
java
Order order = orderMapper.selectById(orderId);
if(OrderStatus.CREATED.equals(order.getStatus())){
return "订单已经创建,请勿重复操作";
}
生产实践总结
高并发接口必须保证幂等,无论前端怎么做,后端一定要有防护手段。
五、缓存击穿:热点key失效瞬间大量请求打穿数据库
业务背景
热点商品数据存入Redis缓存,设置过期时间。缓存过期一瞬间,大量并发请求同时到达,缓存没有数据,全部直接访问数据库,瞬间压垮数据库。
❌错误伪代码
java
public Goods getGoods(Long goodsId){
String json = redis.get("goods:"+goodsId);
if(json != null){
return JSON.parseObject(json,Goods.class);
}
//缓存失效,大量并发同时走到这里查询数据库
Goods goods = goodsMapper.selectById(goodsId);
redis.setex("goods:"+goodsId,300,JSON.toJSONString(goods));
return goods;
}
故障现象
热点key过期瞬间,数据库CPU瞬间飙升,数据库连接被打满,接口大量超时。
解决方案
方案1:互斥锁(分布式锁,防止大量请求同时查库)
缓存失效,抢占分布式锁,只有一个请求去查询数据库回写缓存,其他请求等待缓存生成。
> 缺点:会有少量等待,需要实现分布式锁,注意锁超时释放。
方案2:永不过期(热点key,业务维护更新)
热点商品不设置Redis过期时间,数据库更新的时候同步更新Redis缓存。从根源消除key过期击穿风险。适合静态热点数据。
方案3:缓存逻辑过期(推荐)
Redis里面存数据同时存逻辑过期时间,key物理不过期。线程后台异步更新缓存,其他请求直接返回旧缓存数据。不会阻塞用户请求。
伪代码逻辑:
java
CacheData cache = redis.get(key);
if(cache != null){
//判断逻辑时间是否过期
if(cache.isExpire()){
//开启异步线程更新缓存,直接返回旧数据给用户
threadPool.submit(()->updateCache(goodsId));
}
return cache.getData();
}
//缓存不存在才查数据库
六、线上并发问题排查实战方法论
当线上出现并发类诡异bug,可以按照这套流程定位:
1. 复现:搭建压测环境,JMeter模拟并发,稳定复现问题;
2. 日志:打印线程ID,确认是哪些线程在执行,观察执行时序;
3. 快照:Jstack打印线程堆栈,查看锁等待、死锁;
4. 数据库:查看慢SQL、行锁等待、事务执行时间;
5. 代码审查:寻找读‑写不同步、无锁竞争、无幂等、线程池参数不合理的代码。
> 并发bug不要靠猜,不要盲目加`sleep()`,不要随便加`synchronized`,先定位竞争点。
七、后端并发开发最佳实践总结
1. 数据库并发:读操作和写操作之间存在时间窗口,一定要使用数据库锁、原子SQL、乐观锁,不要依赖业务内存判断;
2. 线程池:拒绝Executors快捷方法,手动定义ThreadPoolExecutor,使用有界队列,配置拒绝策略,自定义线程名,增加监控;
3. 多线程共享变量:分清volatile、Atomic、synchronized各自能力,volatile只解决可见性,不解决原子性;
4. 对外接口:所有写接口必须考虑幂等性,防止重复请求造成业务脏数据;
5. 缓存设计:热点key重点防护,避免缓存击穿,合理设计过期策略;
6. 并发安全优先:优先使用JDK并发工具类,不要使用ArrayList、HashMap在多线程直接修改;
7. 测试:普通单元测试很难暴露并发bug,业务必须补充并发压测。
结尾
并发编程的难点,在于要打破我们单线程的思维习惯。很多代码单看每一行逻辑都没问题,多线程交错执行就会产生意想不到的结果。
很多团队遇到并发故障,采用“打补丁”的方式临时修复,隐患埋藏在系统中。真正的解决方式,是理解底层原理,在编码阶段就规避并发风险。
希望本文的案例可以帮助你在日常开发中避开线上故障。后续会继续分享分布式锁、JVM故障排查、分布式事务等后端硬核实战内容,欢迎收藏阅读。
后端开发` `Java` `并发编程` `性能优化` `程序员` `数据库` `技术实战`

声明:个人原创,仅供参考

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
找了八天才发现她的踪迹,原来她是下山去了

找了八天才发现她的踪迹,原来她是下山去了

坠入二次元的海洋
2026-08-09 00:25:14
震惊!长鑫存储校招线下宣讲名单引热议,网友:有点飘,竟排斥西交大、哈工大等C9院校,北邮、西工大等也旁落

震惊!长鑫存储校招线下宣讲名单引热议,网友:有点飘,竟排斥西交大、哈工大等C9院校,北邮、西工大等也旁落

火山詩话
2026-08-09 07:40:11
打破77年纪录!台风白海豚诡异真身曝光!真正重灾区根本不是浙江

打破77年纪录!台风白海豚诡异真身曝光!真正重灾区根本不是浙江

石辰搞笑日常
2026-08-09 02:20:48
100多人靠换脸替考混进公安和政府,明码标价:省30万乡镇岗位8万

100多人靠换脸替考混进公安和政府,明码标价:省30万乡镇岗位8万

朗威谈星座
2026-08-09 01:33:38
燃油车时代会很快结束?内行人预测:油价很可能是最后的关键!

燃油车时代会很快结束?内行人预测:油价很可能是最后的关键!

离离言几许
2026-08-07 16:09:59
地球最大建筑Terafab落地,马斯克豪赌自由电子激光重写EUV光源

地球最大建筑Terafab落地,马斯克豪赌自由电子激光重写EUV光源

三体引力波
2026-08-08 19:47:50
“白海豚”登陆前,一家四口跑过海边堤坝,9岁男孩被巨浪卷走,当地村民:“是游客,找小路爬了进去”

“白海豚”登陆前,一家四口跑过海边堤坝,9岁男孩被巨浪卷走,当地村民:“是游客,找小路爬了进去”

上观新闻
2026-08-09 08:15:27
烟草公司职工婚内与2人发生不正当关系,还向烟农借款16万 被开除后起诉要求复职,法院判了

烟草公司职工婚内与2人发生不正当关系,还向烟农借款16万 被开除后起诉要求复职,法院判了

红星新闻
2026-08-08 18:33:09
偷鸡不成蚀把米!以为能毁掉原配朱女士,没成想自己先被扒底朝天

偷鸡不成蚀把米!以为能毁掉原配朱女士,没成想自己先被扒底朝天

社会日日鲜
2026-08-08 08:33:29
国内一航班爆发恶性斗殴冲突!飞机正处爬升期,两乘客大打出手!

国内一航班爆发恶性斗殴冲突!飞机正处爬升期,两乘客大打出手!

追踪之点
2026-08-08 22:33:55
脸皮太厚!老头老太太们去星巴克蹭空调,占满座位就是不消费,被店员关空调还不走人,网友:给子女丢脸了!

脸皮太厚!老头老太太们去星巴克蹭空调,占满座位就是不消费,被店员关空调还不走人,网友:给子女丢脸了!

谭谈社会
2026-08-09 00:54:44
赢球输人品?陈熠叫医疗暂停发酵,张本美和赛后回应,全网炸锅

赢球输人品?陈熠叫医疗暂停发酵,张本美和赛后回应,全网炸锅

洲洲影视娱评
2026-08-08 23:01:57
清华教授蔡继明:建议取消“十一”黄金周,将国庆休假调整为1天

清华教授蔡继明:建议取消“十一”黄金周,将国庆休假调整为1天

冰语历史
2026-08-08 00:14:48
国家统计局:7月份居民消费价格同比上涨0.5%

国家统计局:7月份居民消费价格同比上涨0.5%

界面新闻
2026-08-09 09:33:10
鼓励领导干部带头休假之后又撤回文件,到底什么意思嘛?

鼓励领导干部带头休假之后又撤回文件,到底什么意思嘛?

基本常识
2026-08-08 11:43:20
男演员钟宇飞崩溃自曝:富婆带资进组当女主角,50多集短剧强加60余场吻戏......不敢得罪只能强忍

男演员钟宇飞崩溃自曝:富婆带资进组当女主角,50多集短剧强加60余场吻戏......不敢得罪只能强忍

新民周刊
2026-08-08 16:19:55
“中式天庭”视频在海外收获500万播放 创作者竟是成都一名眼镜店老板

“中式天庭”视频在海外收获500万播放 创作者竟是成都一名眼镜店老板

封面新闻
2026-08-08 21:49:11
太牛了!首场直播388.8万人观看!全程不说话,不卖货,关掉打赏

太牛了!首场直播388.8万人观看!全程不说话,不卖货,关掉打赏

动物奇奇怪怪
2026-08-09 03:05:34
90%观众是女孩,场外坐满陪跑爹妈:谁在制造这场少年幻梦?

90%观众是女孩,场外坐满陪跑爹妈:谁在制造这场少年幻梦?

陈意小可爱
2026-08-08 14:58:51
伊媒发布最高领袖穆杰塔巴视频,数月前,该媒体曾发布包含相似片段的视频

伊媒发布最高领袖穆杰塔巴视频,数月前,该媒体曾发布包含相似片段的视频

鲁中晨报
2026-08-09 07:42:06
2026-08-09 09:43:00
情字白头369
情字白头369
个人生活中所拍摄的照片以及成长过程中的心得感悟
12文章数 1关注度
往期回顾 全部

科技要闻

苹果官网:Mac电脑可配合苹果智能使用千问

头条要闻

牛弹琴:伊朗开出6项条件 美军倒吸一口凉气

头条要闻

牛弹琴:伊朗开出6项条件 美军倒吸一口凉气

体育要闻

2-1击败欧联球队!皇马热身赛3胜1平

娱乐要闻

萧敬腾坦白!与大14岁妻子选择丁克

财经要闻

伯克希尔罕见爆买200亿美元股票

汽车要闻

华为乾崑+大六座 星海V6预售8.99万元起

态度原创

数码
房产
时尚
公开课
军事航空

数码要闻

刚刚!苹果官宣千问正式接入Mac,苹果AI入华迈出关键一步

房产要闻

一手信息流出!仁恒雲启西岸开盘,爆卖110套!

夏天裤子容易穿腻,不如试试这几款过膝裙,大方显瘦又百搭

公开课

李玫瑾:为什么性格比能力更重要?

军事要闻

伊朗公布“被击落的美以战机残骸”

无障碍浏览 进入关怀版