curl卡在Connected但无响应,基本就是Tomcat线程池耗尽:TCP已连接,但无空闲线程处理请求,导致健康检查无法进入Controller;常见原因包括外部调用未设超时、DB连接池满、同步IO或锁竞争等阻塞操作。

curl卡在Connected但无响应,基本就是线程池耗尽了
这种现象非常典型:健康检查接口只返回一个整数,curl -v --max-time 3 http://127.0.0.1:8080/health 显示 Connected 后就停住,日志里也不报错。说明TCP连接已建立,但Tomcat没线程可分配给这个请求——线程池已满,新请求只能排队或直接挂起。
此时不是代码逻辑慢,而是请求根本进不到Controller。常见诱因包括:
- 外部HTTP调用(如OkHttp、RestTemplate)未设超时,线程卡在
socketRead0 - 数据库查询慢或连接池满,线程卡在
getConnection或executeQuery - 同步写日志、文件IO、锁竞争等阻塞操作堆积
- 线程池配置过小(如
maxThreads=50),但实际并发远超此数
用jcmd导出线程栈,重点看WAITING和BLOCKED状态
容器里通常没有pgrep或jstack,但jcmd几乎全版本都带。执行:
jcmd 7 Thread.print -l > /tmp/th.txt
其中7是Java进程PID(可用ps -ef | grep '[j]ava'确认)。导出后重点关注:
Linux 安全审计工具,检查 SSH 配置、开放/监听端口、防火墙规则(ufw/iptables/nftables)、失败登录尝试、sudoers 权限...
- 大量线程处于
java.lang.Thread.State: WAITING (parking)→ 通常卡在队列、锁或未唤醒的Condition - 大量线程处于
java.lang.Thread.State: BLOCKED (on object monitor)→ 锁争抢,需结合栈中waiting to lock定位锁对象 - 大量线程停留在
sun.nio.ch.SocketChannelImpl.read或org.apache.http.impl.io.SessionInputBufferImpl.fillBuffer→ 外部调用没超时 - 线程名是
http-nio-8080-exec-XX且数量接近maxThreads→ 确认线程池已打满
Tomcat线程池参数怎么改才不翻车
改配置前先确认当前值。Spring Boot项目中,server.tomcat.max-threads控制maxThreads,但仅调大它不够,必须同步约束上游依赖:
-
maxThreads建议设为CPU核心数 × 2 ~ 4,单机32核建议100~150,不要盲目设到500+ -
accept-count(即acceptCount)不能只看默认100;若前端有Nginx,应与Nginx的worker_connections匹配,避免连接被丢弃 -
min-spare-threads设太低(如默认10)会导致突发流量来临时频繁创建线程,增加GC压力 - 最关键的是:必须限制所有下游调用的超时,比如
RestTemplate要配setConnectTimeout和setReadTimeout,否则线程会永远等下去
改完重启后,用ab -n 1000 -c 200 http://localhost:8080/health快速验证是否仍卡死。
线程池耗尽常和数据库连接池形成“双堵”闭环
线程池卡死很少单独发生。典型链路是:Tomcat线程A去拿DB连接 → 连接池已满 → A线程WAITING → 更多请求进来占满剩余线程 → DB连接更难释放 → 死循环。所以排查必须联动看:
- 查
druid或HikariCP监控页,确认ActiveConnections是否长期≈maxPoolSize - 查慢SQL日志,是否有未加索引的
SELECT *或ORDER BY导致单次查询几百毫秒 - 检查事务范围,
@Transactional是否包了不该包的HTTP调用或大循环 - 注意连接泄漏:比如try-with-resources没覆盖所有分支,或异常时连接未close
真正棘手的点在于,线程栈里看到的“等待DB连接”,未必是DB真慢,而可能是上游某条慢SQL把整个连接池拖垮了。这时候光看线程数没用,得抓活跃连接正在执行什么SQL。

















