Java并发排错速查手册:①卡死→jstack查deadlock/WAITING,检锁序和notify;②数值错→换线程安全类,复合操作加锁或用Atomic;③线程池丢任务→核类型/拒绝策略,submit后必get,shutdown配awaitTermination;④ThreadLocal泄漏→static final声明,业务后remove,禁放大对象。

写一份实用的 Java 并发编程排错速查手册,核心是聚焦高频问题、给出可立即验证的线索和动作,而不是泛泛讲原理。它应该像一张贴在显示器边上的便签:一眼看到现象,三步定位原因,两行代码修复。
一看线程状态就卡死:怀疑死锁或无限等待
现象:应用突然不响应,CPU 不高,JVM 进程还在但无日志输出。
快速排查:
• jstack -l
• 检查是否多个线程按不同顺序获取 synchronized 锁或 ReentrantLock(尤其嵌套调用)
• 查看是否用了 wait() 但没配 notify()/notifyAll(),或用了 Condition.await() 但信号丢失
• 注意 CountDownLatch.await() 或 CyclicBarrier.await() 未被触发的常见场景(比如某个线程抛异常提前退出,没执行 countDown())
数值总不对、对象状态混乱:典型竞态条件
现象:int 计数器偶尔少加、List.add() 后 size 不对、DTO 字段值“凭空”被改。
关键检查点:
• 所有共享变量是否声明为 volatile(仅适用于简单读写,不保证复合操作原子性)
• 是否误用非线程安全类:ArrayList / HashMap / SimpleDateFormat / StringBuilder → 替换为 CopyOnWriteArrayList / ConcurrentHashMap / DateTimeFormatter / StringBuffer
• 复合操作如 if (count == 0) count++ 必须用 synchronized 或 AtomicInteger.compareAndSet()
• 使用 @GuardedBy("lock") 注解(配合 FindBugs/SpotBugs)能静态发现未加锁访问
线程池任务莫名丢失或延迟:配置与使用陷阱
现象:submit 了 Runnable 却没执行;大量任务排队不处理;应用停机时任务被丢弃。
马上核对:
• 线程池类型是否匹配场景:Executors.newCachedThreadPool() 在高并发下可能创建过多线程 OOM;newFixedThreadPool() 队列无界,积压任务吃光内存
• 拒绝策略是否设为 AbortPolicy(默认)却没捕获 RejectedExecutionException
• execute() vs submit():后者返回 Future,若未调用 get(),异常会被吞掉 → 改用 execute() 或确保 future.get() 并捕获 ExecutionException
• shutdown() 后是否漏掉 awaitTermination(),导致 JVM 退出前任务被强制中断
将后端 Java 接口文档转换为前端 TypeScript 类型定义(interface / type)。输入:飞书文档、Spring Boot 接口文档、Swagger/OpenAPI、手写 API 文档等。输出:包含请求参数与响应结果的 .md 文件,含完整 JSDoc 注释。触发关键词:Java 转 TS、接口转换、生成前端类型、接口文档转 TypeScript、DTO/VO/BO 转 interface、后端文档转前端类型、生成接口类型、类型定义生成。
ThreadLocal 值串了或内存泄漏:隐蔽但高频
现象:Web 应用中用户 A 的数据出现在用户 B 的请求里;堆内存持续增长,GC 后不释放。
必须检查:
• ThreadLocal 变量是否是 static final(否则每个实例都持有一份副本,失去复用意义)
• 在线程池场景下,是否每次业务逻辑结束后调用 threadLocal.remove()(尤其 Filter/Interceptor 中)
• 是否把大对象(如 Map、缓存数据)塞进 ThreadLocal → 它会随线程生命周期存在,线程复用即长期持有
• 使用 InheritableThreadLocal 要特别小心,子线程继承后若未清理,同样引发泄漏
不复杂但容易忽略。把这四类问题对应的命令、代码片段、配置项打印出来贴工位,比翻文档快十倍。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










