滴滴一面:在项目中使用多线程时遇到过哪些问题?

发布时间:2026/5/25 21:06:01

滴滴一面:在项目中使用多线程时遇到过哪些问题? 多线程带来的收益往往写在压测曲线上代价则藏在“偶发、难复现、只在生产出”的问题里。项目里遇到的坑大多集中在并发正确性、资源治理、可观测性与性能错觉四个层面。并发正确性最贵的是“偶发错误”数据竞争与可见性同一份共享状态被多个线程读写既可能写丢也可能读到过期值。最常见的表象是计数不准、状态机跳转异常、缓存命中率异常波动。根因通常是缺少互斥与内存可见性保障例如仅靠普通变量传递“已初始化/已关闭”信号。复合操作非原子“先检查再执行”在单线程里很自然在多线程里就是经典竞态余额校验后扣款、库存校验后减库存、Map 里不存在则创建等场景容易出现重复创建、超卖、重复扣减。即便单次读写是原子的复合语义也不是。死锁与锁顺序问题两个线程分别持有锁 A、锁 B 并互相等待表面是接口卡死、线程数上升但吞吐为零。更隐蔽的是“锁顺序不一致”模块各自加锁没问题一旦组合调用就触发环路。活锁、饥饿与优先级反转线程没有被阻塞但一直在“礼让/重试/自旋”CPU 占用很高业务没有推进这是活锁。某类任务长期抢不到锁或线程池资源则是饥饿。实时性场景里低优先级线程持锁导致高优先级线程等待会出现优先级反转。线程生命周期与资源治理不是“开了线程就完了”线程泄漏与失控增长手动 new 线程或不受控的异步提交遇到突发流量会把线程数顶到系统极限上下文切换飙升吞吐下降句柄、栈内存被吃光最终 OOM 或系统拒绝创建新线程。线程池配置不当常见错误包括队列无限大短期“稳定”长期延迟雪崩最终积压到不可恢复队列太小 拒绝策略不当高峰直接丢任务或把调用方拖死I/O 密集与 CPU 密集混用同一线程池I/O 阻塞把 CPU 任务饿死或者 CPU 任务把 I/O 延迟拉长。阻塞调用放错位置把网络 I/O、磁盘 I/O、远程 RPC、数据库查询放在持锁区间或关键线程事件循环、调度线程里会把“局部等待”放大成“全局卡顿”。表现为 P99/P999 延迟突然抬头且伴随线程堆栈集中在同一阻塞点。取消与关闭不完整服务下线、重启、发布时最容易出事任务无法响应中断interrupt或取消信号后台线程未停止导致进程无法退出资源连接、文件、锁未释放出现半关闭状态。性能与容量多线程不等于更快锁竞争与伪共享锁把并发变串行竞争激烈时反而更慢。伪共享false sharing则更隐蔽多个线程频繁写位于同一缓存行的不同变量导致缓存一致性流量暴涨CPU 忙但吞吐上不去。线程数超过硬件并行度线程过多会让系统花大量时间做调度与上下文切换典型症状是CPU 使用率不低但有效工作占比下降RT 抖动变大同样的请求在高并发下反而更慢。错把异步当并行异步只是把等待从调用栈移走不一定带来并行。若最终仍被单个锁、单连接、单队列或单核瓶颈限制线程增多只是在更快地堆积等待。可观测性与排障问题常常“看不见”难复现与不可重放竞态条件依赖时序日志和断点会改变调度导致“加日志就好了”。一些问题只在特定 CPU 核数、特定负载、特定 GC 时机下出现开发环境很难复刻。日志与链路追踪串线并发下使用线程本地变量ThreadLocal承载 traceId、租户信息、用户上下文。如果在线程池中复用线程但未清理会出现链路串号、权限串用、脏上下文污染。指标口径误判看 QPS、平均延迟往往不够多线程问题更常体现在P95/P99 延迟、队列长度、拒绝次数、上下文切换次数锁等待时间、线程池活跃线程数、任务排队时间CPU steal、run queue 长度等系统指标。工程化陷阱从“能跑”到“可靠”差一套规范非线程安全组件被误用常见于缓存、日期格式化器、随机数生成器、连接对象、集合类迭代器等被多个线程共享后出现数据污染或崩溃。问题常被误归因到“网络抖动/数据库慢”实际是并发写坏了内部状态。回调与异常吞掉异步任务的异常如果没有被统一捕获和上报会变成“静默失败”业务看似正常某些任务永远没做完只在对账或数据校验时爆雷。顺序一致性与业务幂等并发消费/并行处理会打乱顺序触发业务假设失效消息重复投递或重试放大并发写入若幂等缺失会造成重复扣款、重复发券、重复入账。典型项目场景速写订单与库存并发扣减导致超卖最终通过“原子扣减 幂等单号 失败补偿”兜底。统计与计费计数器在高并发下偏小或偏大最终替换为分段累加/无锁结构或集中聚合。批处理与导入线程池队列堆积导致内存吃满改为有界队列 背压backpressure限制上游提交速度 分批提交。网关与客户端线程本地上下文未清理导致链路串线改为显式传参或使用作用域清理策略。这些问题的共同点是线程带来的不是“写法变化”而是“时间与状态的组合数爆炸”。多线程代码的风险不在于复杂而在于复杂到无法凭直觉穷举。

相关新闻