关于线程池优雅关闭

使用线程池的问题

程序关闭时(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等。

posted @ 2024-05-21 23:25  帅气的涛啊  阅读(94)  评论(0编辑  收藏  举报