You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ThreadPoolTaskExecutor无法创建新线程致RestTemplate停发请求排查求助

线程池与RestTemplate异常问题排查与修复

一、应用环境与配置

本Java应用依赖及服务版本:

  • Java 7
  • Tomcat 7
  • Spring 4.3.6

核心业务方法:

public void submitSms(final SmppMoSMS smppMoSMS) {
    logger.info("** SMS received from SMSC [{}]", smppMoSMS.toString());
    int i = Thread.activeCount();
    logger.info("Point 1: Active thread count : [{}]", i);
    taskExecutor.execute(new Runnable() {
        @Override
        public void run() {
            try {
                logger.info("Thread start ");
                logger.debug("SMS routing to URL [{}] [{}]", smppMoSMS.getOperator(), smppMoSMS.getApplicationUrl());
                logger.info("SMS routing to URL [{}] [{}]", smppMoSMS.getOperator(), smppMoSMS.getApplicationUrl());
                if (smppMoSMS.getApplicationUrl() == null) {
                    return;
                }
                HttpEntity<SmppMoSMS> entity = new HttpEntity<>(smppMoSMS, headers);
                ResponseEntity<String> response = restTemplate.exchange(smppMoSMS.getApplicationUrl(), HttpMethod.POST, entity, String.class);
                if (HttpStatus.OK == response.getStatusCode()) {
                    logger.info("SMS sending success [{}] [{}] [{}]", smppMoSMS.getOperator(), smppMoSMS.getApplicationUrl(), 200);
                } else {
                    logger.info("SMS sending fail [{}] [{}] [{}]", smppMoSMS.getOperator(), smppMoSMS.getApplicationUrl(), response.getStatusCode());
                }
            } catch (Throwable e) {
                logger.error("SMS sending fail [{}] [{}]", smppMoSMS.getOperator(), smppMoSMS.getApplicationUrl());
                logger.error("SMS ROUTING FAIL", e);
            }
            logger.info("Thread stop ");
        }
    });
    logger.info("Point 2: Active thread count : [{}]", i);
    MDC.clear();
}

TaskExecutor Bean配置:

<bean id="threadPool" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"
      destroy-method="destroy">
    <property name="corePoolSize" value="5"/>
    <property name="maxPoolSize" value="200"/>
    <property name="queueCapacity" value="10000"/>
    <property name="allowCoreThreadTimeOut" value="true"/>
    <property name="waitForTasksToCompleteOnShutdown" value="true"/>
</bean>

RestTemplate Bean配置:

<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
    <constructor-arg>
        <bean class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory">
            <property name="readTimeout" value="30000"/>
            <property name="connectTimeout" value="30000"/>
        </bean>
    </constructor-arg>
</bean>

二、问题现象

  1. 应用启动初期运行正常,RestTemplate可正常发起HTTP请求,日志输出完整;
  2. 运行2-3小时后出现异常:
    • RestTemplate不再生成HTTP请求(已通过Tcpdump及REST API服务器日志确认);
    • 仅输出如下日志:
2022:10:01 07:17:10.623 INFO  [correlationId=1664588830622, sequence=2244] ** SMS received from SMSC [SmppMoSMS{message='Test by
developer ', senderAddress='94XXXXXXX', recipientAddress='94XXXXX',
encoding='0', operator='XXXXXXXX', ncsType='null',
correlationId='1664588830622'}]

2022:10:01 07:17:10.623 INFO  [correlationId=1664588830622,
sequence=2244] Active thread count : [114]

2022:10:01 07:17:10.623 INFO  [correlationId=1664588830622,
sequence=2244] Thread stop

2022:10:01 07:17:10.623 INFO  [correlationId=1664588830622,
sequence=2244] Active thread count : [114]
  • 活跃线程数维持114不再增长;
  1. 重启应用或Tomcat后服务恢复正常。

三、补充说明

该应用已稳定运行多年,近2个月未进行任何代码或配置变更,却突发此问题。推测:即使配置允许创建最多200个线程,线程池也无法创建新线程。


问题解答

1. 导致线程池无法创建新线程的可能原因

  • 线程池队列积压,未触发扩容:ThreadPoolTaskExecutor的逻辑是先填满queueCapacity(10000),才会创建超过corePoolSize的线程。如果队列中积压大量任务,且核心线程都因HTTP请求阻塞(如等待响应超时),新任务只会进入队列,不会触发maxPoolSize扩容。
  • 线程阻塞耗尽线程池:当前RestTemplate未配置HTTP连接池,每次请求都会创建新连接。若目标API响应缓慢或超时,线程会被长时间阻塞(readTimeout=30s),大量线程占用后,线程池无法释放资源,无法创建新线程。
  • JVM内存限制:Java 7中每个线程默认占用1MB栈内存,若JVM内存配置不足(如-Xmx过小),创建新线程时会抛出OutOfMemoryError,导致线程池无法扩容。
  • Tomcat线程池耗尽:Tomcat自身的Connector线程池(maxThreads)若被占满,会间接影响应用内线程池创建新线程的能力。

2. 其他可能的假设

  • RestTemplate连接泄漏:HttpComponentsClientHttpRequestFactory若未正确释放连接,会导致连接耗尽,后续请求无法建立连接,线程阻塞在获取连接阶段。
  • 目标API服务异常:目标REST API服务器故障,返回慢或无响应,导致大量线程阻塞在HTTP请求环节,线程池被占满。
  • MDC内存泄漏:主线程执行MDC.clear(),但子线程继承了MDC内容,若未及时清理,会导致线程上下文无法回收,间接影响线程池。
  • JVM内存泄漏:长时间运行后应用存在内存泄漏(如未释放的对象引用),导致JVM频繁GC甚至OOM,影响线程创建。

3. 进一步排查方法

  • 线程dump分析:出现问题时执行jstack <pid>获取线程栈,查看是否大量线程处于WAITING/TIMED_WAITING状态,是否卡在RestTemplate请求步骤。
  • 内存dump分析:用jmap -dump:format=b,file=heap.bin <pid>导出堆内存,分析是否存在内存泄漏,以及线程池队列的任务数量。
  • 线程池指标监控:自定义监控或用Spring Actuator,实时监控corePoolSize、activeCount、queueSize等指标,确认队列是否已满、线程是否阻塞。
  • 目标API状态排查:检查目标API服务器日志,确认请求是否到达、是否存在响应缓慢;用curl/postman测试目标API响应时间。
  • JVM配置检查:查看JVM的-Xmx、-Xms、-Xss参数,确认内存是否足够创建新线程;查看GC日志,确认是否有频繁Full GC或OOM。
  • HTTP连接状态监控:用netstat/ss命令查看应用与目标API的连接数,确认是否存在大量TIME_WAIT或ESTABLISHED状态的连接。

4. 修复建议

  • 给RestTemplate配置HTTP连接池:限制最大连接数,避免每次请求创建新连接,减少线程阻塞:
<bean id="httpClient" class="org.apache.http.impl.client.HttpClients">
    <factory-method name="custom">
        <bean class="org.apache.http.impl.client.HttpClientBuilder">
            <property name="maxConnTotal" value="200"/>
            <property name="maxConnPerRoute" value="100"/>
        </bean>
    </factory-method>
</bean>

<bean id="requestFactory" class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory">
    <constructor-arg ref="httpClient"/>
    <property name="readTimeout" value="30000"/>
    <property name="connectTimeout" value="30000"/>
</bean>

<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
    <constructor-arg ref="requestFactory"/>
</bean>
  • 调整线程池参数:减小队列容量,触发线程扩容;设置空闲线程存活时间,及时回收资源:
<bean id="threadPool" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"
      destroy-method="destroy">
    <property name="corePoolSize" value="10"/>
    <property name="maxPoolSize" value="200"/>
    <property name="queueCapacity" value="1000"/> <!-- 减小队列容量,触发线程扩容 -->
    <property name="keepAliveSeconds" value="60"/>
    <property name="allowCoreThreadTimeOut" value="true"/>
    <property name="waitForTasksToCompleteOnShutdown" value="true"/>
</bean>
  • 优化业务逻辑:在子线程末尾添加MDC.clear();提前过滤applicationUrl为空的任务;为HTTP请求添加额外超时保护。
  • 添加监控告警:集成监控工具(如Prometheus+Grafana),监控线程池状态、HTTP请求成功率等指标,设置告警阈值。
  • 升级依赖版本:考虑升级Java(Java7已停止支持)、Spring和Tomcat版本,修复旧版本中线程池或HTTP客户端的已知bug。

内容的提问来源于stack exchange,提问作者Udara Seneviratne

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 23:35:39