關於線程池優雅關閉

使用線程池的問題

程序關閉時(eg. 上線),線程池中的任務會丟失(內存中)。

線程池優雅關閉

利用Spring中ContextClosedEvent:關閉程序觸發的事件,在使用線程池的地方,可以將線程池註冊到ThreadPoolShutdownListener中,然後在程序關閉時,ThreadPoolShutdownListener會監聽ContextClosedEvent事件,執行線程池的優雅關閉操作。這樣可以避免程序關閉時線程池中任務丟失的問題。

注:優雅關閉只能簡單處理任務未大量堆積的線程池,任務大量堆積延遲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等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章