当前位置: 首页 > news >正文

《Java 100 天进阶之路》第60.3篇:多线程生产环境避坑清单(2026版)

第60.3篇:多线程生产环境避坑清单(2026版)

📌系列导航:《Java 100 天进阶之路》完整目录 |
⬅️ 上一篇:第60.2篇:多线程面试官追问陷阱合集 |
➡️ 下一篇:第60.4篇:多线程代码示例集

🗺️ 本文阅读地图(2 分钟速览)

多线程开发中,99% 的线上问题都集中在少数几个坑点上。本文汇总 20+ 个生产环境真实踩坑案例,每条配错误案例 + 正确做法,直接落地到代码规范:

分类坑点数量
线程池陷阱5 条
锁与并发工具陷阱4 条
异步编程陷阱3 条
原子类与 CAS 陷阱3 条
ThreadLocal 陷阱4 条
综合排查陷阱3 条

文章目录

    • 第60.3篇:多线程生产环境避坑清单(2026版)
      • 🗺️ 本文阅读地图(2 分钟速览)
      • 一、线程池陷阱(5条)
        • ❌ 坑1:Executors 创建线程池
        • ❌ 坑2:线程池未设置线程名
        • ❌ 坑3:submit 异常被吞没
        • ❌ 坑4:线程池不关闭
        • ❌ 坑5:IO 密集型线程数设置过大/过小
      • 二、锁与并发工具陷阱(4条)
        • ❌ 坑6:ReentrantLock 不 finally unlock
        • ❌ 坑7:synchronized 锁对象用 String 常量
        • ❌ 坑8:CountDownLatch 计数器不归零
        • ❌ 坑9:死锁只用 jstack 不改代码
      • 三、异步编程陷阱(3条)
        • ❌ 坑10:CompletableFuture 不设超时
        • ❌ 坑11:CompletableFuture 异常不处理
        • ❌ 坑12:用 commonPool 做业务异步
      • 四、原子类与 CAS 陷阱(3条)
        • ❌ 坑13:volatile 做计数器
        • ❌ 坑14:ABA 问题
        • ❌ 坑15:LongAdder 做精确计算
      • 五、ThreadLocal 陷阱(4条)
        • ❌ 坑16:ThreadLocal 用完不 remove
        • ❌ 坑17:ThreadLocal 存大对象
        • ❌ 坑18:线程池用 InheritableThreadLocal
        • ❌ 坑19:set(null) 替代 remove
      • 六、综合排查陷阱(3条)
        • ❌ 坑20:jstack 抓不到间歇性死锁
        • ❌ 坑21:数据库连接池死锁
        • ❌ 坑22:多线程共用 HashMap
    • 📊 你的学习进度
    • 👉 下一篇文章预告

一、线程池陷阱(5条)

❌ 坑1:Executors 创建线程池
// ❌ 错误ExecutorServicepool=Executors.newFixedThreadPool(10);// 无界队列 → 任务堆积 → OOM
// ✅ 正确:手动创建有界线程池ExecutorServicepool=newThreadPoolExecutor(10,20,60L,TimeUnit.SECONDS,newArrayBlockingQueue<>(100),newThreadFactoryBuilder().setNameFormat("biz-%d").build(),newThreadPoolExecutor.CallerRunsPolicy());
❌ 坑2:线程池未设置线程名
// ❌ 错误:线程名默认 pool-1-thread-1,无法定位业务ExecutorServicepool=Executors.newFixedThreadPool(10);
// ✅ 正确:自定义 ThreadFactoryThreadFactoryfactory=r->newThread(r,"order-pool-"+System.currentTimeMillis());
❌ 坑3:submit 异常被吞没
// ❌ 错误:submit 不处理 Future,异常静默消失pool.submit(()->{thrownewRuntimeException();});
// ✅ 正确:处理 Future 或使用 executeFuture<?>future=pool.submit(task);try{future.get();}catch(ExecutionExceptione){log.error("",e);}// 或使用 execute(异常直接抛出)pool.execute(task);
❌ 坑4:线程池不关闭
// ❌ 错误:程序退出时线程池未关闭,资源泄漏ExecutorServicepool=Executors.newFixedThreadPool(10);pool.submit(task);
// ✅ 正确:优雅关闭try{pool.shutdown();if(!pool.awaitTermination(60,TimeUnit.SECONDS))pool.shutdownNow();}catch(InterruptedExceptione){pool.shutdownNow();}
❌ 坑5:IO 密集型线程数设置过大/过小
// ❌ 错误:未区分 CPU/IO 类型newThreadPoolExecutor(4,4,...);// 假设 CPU 4 核,IO 密集型配置不足
// ✅ 正确:IO 密集型 = CPU 核数 × 2 ~ 4intioCore=Runtime.getRuntime().availableProcessors()*2;newThreadPoolExecutor(ioCore,ioCore*2,...);

二、锁与并发工具陷阱(4条)

❌ 坑6:ReentrantLock 不 finally unlock
// ❌ 错误:异常时锁不释放lock.lock();// 业务逻辑抛异常 → 死锁lock.unlock();
// ✅ 正确:finally 释放lock.lock();try{/* 业务 */}finally{lock.unlock();}
❌ 坑7:synchronized 锁对象用 String 常量
// ❌ 错误:不同模块共享同一字符串常量池StringLOCK="LOCK";synchronized(LOCK){/* 其他模块也用 "LOCK" 意外死锁 */}
// ✅ 正确:使用专用 ObjectObjectlock=newObject();synchronized(lock){/* 只锁当前模块 */}
❌ 坑8:CountDownLatch 计数器不归零
// ❌ 错误:异常时 countDown 未执行 → 主线程永久阻塞CountDownLatchlatch=newCountDownLatch(10);for(inti=0;i<10;i++){pool.submit(()->{try{/* 业务 */}finally{latch.countDown();}});}latch.await();// 某个任务异常退出 → 永久阻塞
// ✅ 正确:await 带超时if(!latch.await(10,TimeUnit.SECONDS)){log.error("超时");}
❌ 坑9:死锁只用 jstack 不改代码
// ❌ 错误:只发现死锁不分析根因// 输出 Found one Java-level deadlock,改完又出现
// ✅ 正确:分析锁顺序,统一获取顺序或改用 tryLock// 方案:所有线程按固定顺序获取锁(先 lockA 再 lockB)

三、异步编程陷阱(3条)

❌ 坑10:CompletableFuture 不设超时
// ❌ 错误:依赖服务卡死 → 线程永远阻塞CompletableFuture.supplyAsync(()->callSlowApi()).join();
// ✅ 正确:设超时 + 兜底CompletableFuture.supplyAsync(()->callSlowApi()).completeOnTimeout("fallback",3,TimeUnit.SECONDS).join();
❌ 坑11:CompletableFuture 异常不处理
// ❌ 错误:异常静默消失,排查极难CompletableFuture.supplyAsync(()->{thrownewRuntimeException();}).thenAccept(System.out::println);// 异常被吞
// ✅ 正确:异常处理 + 日志CompletableFuture.supplyAsync(()->{thrownewRuntimeException();}).exceptionally(e->{log.error("",e);returnnull;}).thenAccept(System.out::println);
❌ 坑12:用 commonPool 做业务异步
// ❌ 错误:业务异步用默认线程池CompletableFuture.supplyAsync(()->callApi());// 所有模块共用 commonPool → 互相影响
// ✅ 正确:自建业务线程池CompletableFuture.supplyAsync(()->callApi(),AsyncPool.IO_POOL);

四、原子类与 CAS 陷阱(3条)

❌ 坑13:volatile 做计数器
// ❌ 错误:多线程 count++,结果小于预期volatileintcount=0;// 100 线程各加 100 次,结果 < 10000
// ✅ 正确:用 AtomicInteger 或 LongAdderAtomicIntegercount=newAtomicInteger(0);count.incrementAndGet();
❌ 坑14:ABA 问题
// ❌ 错误:无锁栈/队列直接用 AtomicReferenceAtomicReference<Node>stack=newAtomicReference<>();// 可能发生 ABA → 链表断裂
// ✅ 正确:用 AtomicStampedReferenceAtomicStampedReference<Node>stack=newAtomicStampedReference<>(null,0);
❌ 坑15:LongAdder 做精确计算
// ❌ 错误:用 LongAdder 做精确统计LongAdderadder=newLongAdder();longtotal=adder.sum();// 弱一致性,累加过程有并发修改
// ✅ 正确:需要精确值用 AtomicLong 或加锁AtomicLongcounter=newAtomicLong(0);longexact=counter.get();// 强一致性

五、ThreadLocal 陷阱(4条)

❌ 坑16:ThreadLocal 用完不 remove
// ❌ 错误:线程池场景忘记清理,脏数据 + 内存泄漏ThreadLocal<String>context=newThreadLocal<>();context.set(userId);// 任务结束不 remove → 下一个请求读到旧 userId
// ✅ 正确:try-finally 保证 removetry{context.set(userId);// 业务}finally{context.remove();// 必须!}
❌ 坑17:ThreadLocal 存大对象
// ❌ 错误:存大对象导致单线程内存占用高ThreadLocal<byte[]>cache=newThreadLocal<>();cache.set(newbyte[1024*1024]);// 每个线程 1MB// 线程池 100 线程 → 100MB 内存泄漏
// ✅ 正确:只存轻量级 key,大对象放 Redis/缓存ThreadLocal<String>cacheKey=newThreadLocal<>();Stringkey="user:"+userId;// 大对象从外部缓存获取
❌ 坑18:线程池用 InheritableThreadLocal
// ❌ 错误:InheritableThreadLocal 不适用于线程池InheritableThreadLocal<String>tl=newInheritableThreadLocal<>();tl.set("parent");pool.submit(()->System.out.println(tl.get()));// 可能为 null
// ✅ 正确:用 TransmittableThreadLocal(阿里开源)TransmittableThreadLocal<String>ttl=newTransmittableThreadLocal<>();ttl.set("parent");Runnabletask=TtlRunnable.get(()->System.out.println(ttl.get()));pool.submit(task);
❌ 坑19:set(null) 替代 remove
// ❌ 错误:set(null) 不删除 Entry,内存泄漏仍然存在context.set(null);// Entry 还在,只是 value 为 null
// ✅ 正确:必须 remove()context.remove();// 删除整个 Entry

六、综合排查陷阱(3条)

❌ 坑20:jstack 抓不到间歇性死锁
// ❌ 错误:抓一次没看到 deadlock 就认为没问题// 间歇性死锁只在特定时序下触发
// ✅ 正确:多次抓取 + 开启 JVM 参数// -XX:+PrintConcurrentLocks 打印锁信息// 或使用 Arthas thread -b 实时检测
❌ 坑21:数据库连接池死锁
// ❌ 错误:所有连接被占满,新请求等待 → 但等待线程持有其他资源// 连接池大小 < 并发请求数 → 死锁
// ✅ 正确:设置合理的连接池大小 + 超时// 每个请求的 SQL 执行时间 < 连接超时// 监控连接池使用率,提前扩容
❌ 坑22:多线程共用 HashMap
// ❌ 错误:多线程直接写 HashMapMap<String,String>map=newHashMap<>();map.put("key","value");// 并发 → 死循环/数据丢失
// ✅ 正确:用 ConcurrentHashMapMap<String,String>map=newConcurrentHashMap<>();map.put("key","value");

📊 你的学习进度

  • 当前:第60.3篇 ·多线程专题外挂:生产环境避坑清单
  • ✅ 已完成:基础篇44篇 + 第45~60篇 + 第60.1~60.3篇
  • 📖 正在学:第60.3篇
  • ⏳ 待学习:第60.4篇(代码示例集)+ 第61~108篇

👉 📚 完整目录 & 学习指南 | 🔥 订阅本专栏,不错过每一篇


👉 下一篇文章预告

🚀下一篇:《第60.4篇:多线程代码示例集》

内容简介:面试可直接背诵的多线程代码模板,覆盖线程创建、锁使用、线程池、异步编排、CAS 等所有场景!

📌《Java 100 天进阶之路 | 从入门到上岗就业》每天一篇,建议收藏 + 关注,一起100天拿offer!
👉 点击关注我,更新后第一时间收到推送!

http://www.jsqmd.com/news/1195930/

相关文章:

  • 陈,新型小鼠代谢笼、新型大鼠代谢笼
  • 从赛题到实战:剖析三端口DC-DC变换器的双模式能量管理策略
  • 【蓝牙精品系列文档】【04_blemesh model】03_业务模型层
  • AMBA-APB各版本的信号区别和传输示例
  • 指令重排序
  • 手把手教你实现九宫格抽奖特效:前端开发实战指南
  • JAVA基于MC协议实现三菱PLC数据采集与监控
  • Postman便携版:重新定义你的API测试工作流
  • 预算有限怎么做GEO优化?2026高性价比GEO监测工具与入
  • Modbus-RTU协议(从零到一实战篇)
  • 阿里云 RAM 子账号怎么绑定虚拟 MFA?手把手教程
  • 大模型脑机接口:SAE解剖黑盒之谜
  • skill从入门到精通
  • CVA6+ARA通过cheshire平台部署到U280-2
  • 08-LCD1602驱动实战:从时序解析到自定义字符显示
  • day16:极限四则运算法则与复合函数极限
  • 从零搭建企业级静态网页:前端开发者的 MSDN 式指南
  • 2026年度GEO服务商竞争力榜单:服务稳定性与优化效果综合评估
  • 10个即插即用的R语言效率提升技巧
  • AT89S52单片机定时器:从基础结构到精准应用实战
  • 绍兴越城区打井多少钱一米,越城打井电话,老小区水压不够打井 - 瑞溪泉水利
  • 2026年云计算运维实战:Docker与Kubernetes全链路技能指南
  • HarmonyOS7尾部吸附综合页实战:End 吸附策略与多配置综合演示
  • 在线学习平台
  • STM32F103通过USART串口指令实现多LED状态控制
  • FUEL算法调优实战:从参数解析到场景适配的探索效率提升
  • 嵌入式开发中GPMC时序配置详解:从原理到实战
  • IKFast机械臂逆运动学求解器实战:从5微秒响应到MoveIt!插件集成
  • 高级java每日一道面试题-2026年04月10日-实战篇[Docker]-如何排查容器之间的依赖问题?
  • 基于Stable Diffusion的水果艺术风格转换:浮一大果项目实战