使用線程池的問題
程序關閉時(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等。