线程池任务假死通常源于下游资源卡住,最典型的是数据库连接池耗尽:业务线程阻塞在getConnection()、大量线程处于TIMED_WAITING/WAITING状态、连接池活跃数达上限且等待线程数持续上涨,进而导致新请求无法分配线程而整体假死。

线程池任务假死,常不是线程池本身的问题,而是下游资源(比如数据库连接池)卡住后引发的连锁反应。最典型的就是:数据库连接池耗尽 → 业务线程在 getConnection() 上无限等待 → 线程池中可用线程被逐步占满 → 新请求无法分配线程 → 整体服务“假死”。排查需顺着这个链路逐层验证。
看线程堆栈,确认是否卡在获取连接上
用 jstack <pid> 抓取线程快照,重点搜索以下关键词:
-
DataSource.getConnection、HikariPool.getConnection或DruidDataSource.getConnection -
await、park、timed_wait等阻塞态关键字 - 大量线程处于
TIMED_WAITING或WAITING状态,且堆栈停留在连接获取环节
如果发现数十个线程都停在这一步,基本可断定是连接池已无可用连接,后续线程只能排队等待。
查连接池实时状态,验证是否真爆满
不要只看配置值,要读运行时指标。以主流连接池为例:
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
-
HikariCP:通过 JMX 或代码调用
HikariPoolMXBeangetActiveConnections()(活跃数)、getIdleConnections()(空闲数)、getThreadsAwaitingConnection()(等待线程数)——若活跃=最大值、空闲=0、等待数持续上涨,就是爆满实锤 -
Druid:访问内置监控页面
/druid/index.html,或查 JMX 中PoolingDataSource的ActiveCount、PoolingCount、WaitThreadCount
注意:即使数据库服务器显示连接数不高,也不能排除应用端连接池已满——因为连接池是应用进程内的资源,和 DB 层的物理连接数不是一一映射关系。
定位连接泄漏或慢SQL,揪出根因
连接池满只是表象,背后通常是两类问题:
-
连接泄漏:未正确关闭
Connection/Statement/ResultSet。开启连接泄漏检测(如 Hikari 的leak-detection-threshold=60000),日志中会打印泄漏堆栈;Druid 可配remove-abandoned-timeout并开启log-abandoned -
慢SQL/锁竞争:单条 SQL 执行太久(如全表扫描、没走索引),或高并发更新同一行(如乐观锁热点更新),导致连接长期被占用。用
show processlist(MySQL)或pg_stat_activity(PostgreSQL)看 DB 层哪些连接处于Updating或Locked状态,并关联慢日志分析
特别提醒:MyBatis 默认不设 SQL 超时,若数据库响应慢,连接就会一直挂着。务必配置 defaultStatementTimeout 或在 Mapper 中显式指定 timeout。
检查线程池配置与隔离,避免雪崩扩散
即使数据库出问题,也不该让所有业务线程一起卡死。关键检查点:
- 是否为数据库操作单独配置了线程池?还是混用公共线程池(如 Tomcat 的
Executor)?建议 IO 型 DB 操作使用独立线程池,避免拖垮 HTTP 请求处理线程 - 线程池拒绝策略是否合理?用
AbortPolicy会抛异常便于告警;避免用CallerRunsPolicy,它会让调用线程自己执行任务,可能把 Web 容器线程也拖进阻塞 - 线程池队列是否无界?无界队列 + 长时间阻塞任务 = 内存缓慢增长 + 任务越积越多。推荐用有界队列 + 合理拒绝策略
不复杂但容易忽略。

















