优雅关闭线程池:shutdown vs shutdownNow🛑

79 阅读2分钟

关闭线程池不是简单的shutdown(),而是一套组合拳:拒绝新任务→等待完成→强制关闭→清理资源!

一、两种关闭方式对比

shutdown() - 温柔派

executor.shutdown();

行为:

  1. ✅ 不再接受新任务
  2. ✅ 执行已提交的任务(队列中的继续执行)
  3. ⏳ 等待所有任务完成

生活类比: 餐厅打烊🍽️

  • 不接新客人
  • 服务完已经点单的客人
  • 等所有客人吃完

shutdownNow() - 强硬派

List<Runnable> notExecuted = executor.shutdownNow();

行为:

  1. ✅ 不再接受新任务
  2. ⚠️ 尝试中断正在执行的任务
  3. ❌ 队列中的任务不执行,直接返回

生活类比: 餐厅紧急疏散🚨

  • 不接新客人
  • 正在吃的客人也请离开
  • 已点单但未做的菜不做了

对比表

特性shutdownshutdownNow
新任务❌ 拒绝❌ 拒绝
队列中的任务✅ 执行❌ 不执行,返回列表
正在执行的任务✅ 继续⚠️ 尝试中断
返回值未执行的任务列表

二、标准关闭流程(推荐)⭐

public void shutdownGracefully(ExecutorService executor) {
    log.info("开始关闭线程池...");
    
    // 1. 停止接受新任务
    executor.shutdown();
    
    try {
        // 2. 等待60秒
        if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
            
            log.warn("等待超时,强制关闭");
            
            // 3. 强制关闭
            List<Runnable> droppedTasks = executor.shutdownNow();
            log.warn("丢弃任务数: {}", droppedTasks.size());
            
            // 4. 再等待30秒
            if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
                log.error("线程池无法关闭");
            }
        }
    } catch (InterruptedException e) {
        log.error("关闭被中断");
        executor.shutdownNow();
        Thread.currentThread().interrupt();
    }
    
    log.info("线程池已关闭");
}

三、完整示例

public class ThreadPoolShutdownDemo {
    
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            2, 5, 60L, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(10),
            new ThreadPoolExecutor.CallerRunsPolicy()
        );
        
        // 提交10个任务
        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            executor.execute(() -> {
                try {
                    System.out.println("任务" + taskId + "开始执行");
                    Thread.sleep(2000);
                    System.out.println("任务" + taskId + "执行完成");
                } catch (InterruptedException e) {
                    System.out.println("任务" + taskId + "被中断");
                }
            });
        }
        
        Thread.sleep(1000);
        
        // 优雅关闭
        shutdownGracefully(executor);
    }
    
    private static void shutdownGracefully(ExecutorService executor) {
        executor.shutdown();
        
        try {
            if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
                List<Runnable> dropped = executor.shutdownNow();
                System.out.println("强制关闭,丢弃" + dropped.size() + "个任务");
                
                executor.awaitTermination(2, TimeUnit.SECONDS);
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
        
        System.out.println("线程池已关闭");
    }
}

四、Spring Boot优雅停机

@Configuration
public class ThreadPoolConfig {
    
    @Bean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(50);
        executor.setQueueCapacity(1000);
        executor.setThreadNamePrefix("async-");
        
        // 等待任务完成后再关闭
        executor.setWaitForTasksToCompleteOnShutdown(true);
        
        // 最多等待60秒
        executor.setAwaitTerminationSeconds(60);
        
        executor.initialize();
        return executor;
    }
}

五、面试高频问答💯

Q: shutdown和shutdownNow的区别?

A:

  • shutdown:优雅关闭,执行完队列中的任务
  • shutdownNow:强制关闭,尝试中断正在执行的任务

Q: shutdownNow能立即停止任务吗?

A: 不能! 只是发送中断信号,任务需要响应中断才能停止。

Q: 如何判断线程池是否关闭?

A:

executor.isShutdown();     // 是否调用了shutdown
executor.isTerminated();   // 是否所有任务都完成

Q: 线程池关闭后还能提交任务吗?

A: 不能,会抛出RejectedExecutionException


下一篇→ ThreadLocalRandom为啥比Random快?🎲