关于线程池优雅关闭
使用线程池的问题
程序关闭时(eg. 上线),线程池中的任务会丢失(内存中)。
线程池优雅关闭
利用Spring中ContextClosedEvent
:关闭程序触发的事件,在使用线程池的地方,可以将线程池注册到ThreadPoolShutdownListener中,然后在程序关闭时,ThreadPoolShutdownListener会监听ContextClosedEvent事件,执行线程池的优雅关闭操作。这样可以避免程序关闭时线程池中任务丢失的问题。
注:优雅关闭只能简单处理任务未大量堆积的线程池,利用延迟n秒关闭进行消费剩余任务的原理(不会有新产生的任务。为什么不会有新任务的产生?部署新节点后,注册中心、负载均衡配置会逐步移除旧节点,没有新的请求,就不会产生新的线程任务)。当任务大量堆积导致延迟n秒关闭程序也不能消费完,那么还会产生任务丢失。
package org.example.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @author: handsometaoa
* @description
* @date: 2024/5/20 23:01
*/
@Slf4j
@Component
public class ThreadPoolShutdownListener implements ApplicationListener<ContextClosedEvent> {
private static final long DEFAULT_AWAIT_TERMINATION = 20;
private long awaitTerminationSeconds = DEFAULT_AWAIT_TERMINATION;
private final List<ExecutorService> threadPools = new ArrayList<>();
/**
* 注册线程池
*
* @param executor 线程池
*/
public void registerExecutor(ExecutorService executor) {
threadPools.add(executor);
}
/**
* 修改等待结束时间
*
* @param awaitTerminationSeconds 线程等待时间(单位/秒)
*/
public void setAwaitTerminationSeconds(long awaitTerminationSeconds) {
this.awaitTerminationSeconds = awaitTerminationSeconds;
}
@Override
public void onApplicationEvent(@NonNull ContextClosedEvent event) {
log.info("程序关闭中,共计{}个线程池优雅关闭开始...", threadPools.size());
if (CollectionUtils.isEmpty(threadPools)) {
return;
}
for (ExecutorService pool : threadPools) {
pool.shutdown();
try {
if (!pool.awaitTermination(awaitTerminationSeconds, TimeUnit.SECONDS)) {
log.warn("线程池{}在{}秒内未关闭,强制关闭", pool, awaitTerminationSeconds);
}
} catch (InterruptedException e) {
log.error("线程池{}关闭时发生异常", pool, e);
Thread.currentThread().interrupt();
}
}
log.info("程序关闭中,线程池优雅关闭结束...");
}
}
总结
要避免不丢数据,可以使用消息队列处理。RocketMQ、Kafka、RabbitMQ、Redis等。
本文来自博客园,作者:帅气的涛啊,转载请注明原文链接:https://www.cnblogs.com/handsometaoa/p/18205176