组件线程没有关闭导致tomcat无法关闭

组件线程没有关闭导致tomcat无法关闭

本文记录实际项目中使用线程时遇到的相关问题,阐述、分析了产生的问题,最终给出解决方案。

问题描述

Tomcat状态监控页面,点击停止按钮,页面一直显示正在停止,后台进程也一直显示正在停止,通过jstack分析,Tomcat关不掉是因为组件线程没有正常关闭导致

1
2
3
4
5
6
7
8
9
10
11
12
13
"pool-5-thread-1" #89 prio=5 os_pion=0 tid=0x000000003561b800 nid=0x1743c waiting on condition [0x000000004413e000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000073a308cb0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at com.*.*.*.notify.consumer.datadictionary.ListenerEventQueue.takeDataDictionary(ListenerEventQueue.java:2039)
at com.*.*.*.notify.consumer.datadictionary.DatadictionaryEventConsumer.updateDatadictionary(DatadictionaryEventConsumer.java:62)
at com.*.*.*.notify.consumer.datadictionary.AbstractEventConsumer.run(AbstractEventConsumer.java:64)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)

问题分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class WebContextLoaderListener implements ServletContextLinstener {
private Logger log = LoggerFactory.getLogger(WebContextLoaderListener.class);
private ExecutorService executorService = Executors.newSingleThreadExecutor();
@Override
public void contextInitialized(ServletContextEvent sce){
try{
//启动事件消费任务
Map<String, AbstractEventConsumer> eventConsumerList = AppContext.getBeanOfType(AbstractEventConsumer.class);
if(MapUtils.isNotEmpty(eventConsumerList)){
for (AbstractEventConsumer consumer : eventConsumerList.values()){
if(consumer != null){
executorService.execute(consumer);
}
}
}
}
catch(Exception){
log.error(*Log.toLog(ErrorCodeEnum.UNKNOW_ERROR.getCodeStr(), "WebContextLoaderListener init fail"), e);
}
}
@Override
public void contextDestroyed(ServletContextEvent sce){
//关闭线程池
if(executorService != null && !executorService .isShutdown()){
executorService.shutdown();
}
}
}

tomcat停止时,有执行销毁线程池操作,但线程并没有关闭。实际是executorService.shutdown()这种关闭线程的方式存在问题;

  • 调用shutdownNow()后,不可以再提交新的task(executorService.execute()操作),已经提交的将继续执行,当所有线程结束执行当前任务,executorService才会真正关闭。
  • 调用shutdown()后,试图停止当前正在执行的task,并返回尚未执行的task列表。

解决方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public void contextDestroyed(ServletContextEvent sce){
//关闭线程池
if(executorService != null && !executorService .isShutdown()){
executorService.shutdown();
try{
if(!executorService.awaitTermination(2, TimeUnit.SECONDS)){
executorService.shutdownNow();
}
}
catch(InterruptedException e){
executorService.shutdownNow();
}
}
}

其他问题

阿里巴巴的JAva开发手册有这样一条规范:

【强制】 线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2) CachedThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

Spring core包提供的ThreadPoolTaskExecutor对ThreadPoolExecutor进行了封装处理,项目中线程池配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<bean id="consumerThreadPool" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<!-- 线程池的名称前缀 -->
<property name="threadNamePrefix" value="issc-mq-message-consumer-threadpool-" />
<!-- 设置为守护线程 -->
<property name="daemon" value="true" />
<!-- 线程池维护线程的最少数量 -->
<property name="corePoolSize" value="5" />
<!-- 线程池维护线程的最大数量 -->
<property name="maxPoolSize" value="10" />
<!-- 缓存队列的长度 -->
<property name="queueCapacity" value="10" />
<!-- 允许的空闲时间 -->
<property name="keepAliveSeconds" value="90" />
<!-- 对拒绝task的处理策略 -->
<property name="rejectedExecutionHandler">
<bean class="java.util.concurrent.ThreadPoolExecutor$AbortPolicy" />
</property>
</bean>

ThreadPoolTaskExecutor的处理流程:

  • 当线程数量小于corePoolSize,新建线程处理请求
  • 当线程数量等于corePoolSize,把请求放入blockingQueue中,线程池中的空闲线程从blockingQueue中获取任务并处理
  • 当blockingQueue满了,会新建线程处理请求,如果线程数量等于maxPoolSize,使用rejectedExecutionHandler做拒绝处理
  • 当线程数量大于corePoolSize时,多于的线程会等待keepAliveSeconds时间,如果无请求处理就自行销毁

修改后代码如下,设置为守护线程后,tomcat关闭时无需手动关闭线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class WebContextLoaderListener implements ServletContextLinstener {
private Logger log = LoggerFactory.getLogger(WebContextLoaderListener.class);
@Override
public void contextInitialized(ServletContextEvent sce){
try{
//启动事件消费任务
TaskExecutor consumerThreadPool= AppContext.getBean("consumerThreadPool");
if(consumerThreadPool != null){
Map<String, AbstractEventConsumer> eventConsumerList = AppContext.getBean(AbstractEventConsumer.class);
if(MapUtils.isNotEmpty(eventConsumerList)){
for (AbstractEventConsumer consumer : eventConsumerList.values()){
if(consumer != null){
executorService.execute(consumer);
}
}
}
}
}
catch(Exception){
log.error(*Log.toLog(ErrorCodeEnum.UNKNOW_ERROR.getCodeStr(), "WebContextLoaderListener init fail"), e);
}
}
}

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×