← 返回题目列表

线程池有哪几种状态?shutdown 和 shutdownNow 有什么区别?

中等 第 20 / 31 题 更新于 2026/07/27
线程池状态shutdown生命周期

简化版

ThreadPoolExecutor5 种状态,按生命周期推进:RUNNING(运行中,能接新任务、能处理队列任务)→ SHUTDOWN(调 shutdown(),不接新任务,但会处理完队列里剩余的任务)→ STOP(调 shutdownNow(),不接新任务、也不处理队列、还会中断正在执行的任务)→ TIDYING(所有任务终止、工作线程数为 0,即将进入终结)→ TERMINATEDterminated() 钩子执行完,彻底结束)。关闭线程池的两个方法核心区别:shutdown()「温和关闭」——不接新任务,但把队列里已有的任务执行完再关(返回 void);shutdownNow()「强制关闭」——不接新任务、清空队列、中断正在执行的任务,返回「队列里还没执行的任务列表」。一句话:shutdown 优雅(做完存量再关)、shutdownNow 强制(立即停、返回未执行任务)

详细版

线程池的 5 种状态

RUNNING → SHUTDOWN → TIDYING → TERMINATED
   ↓                    ↑
  STOP ─────────────────┘

RUNNING:能接新任务、能处理队列任务(线程池创建后的初始状态)
SHUTDOWN:不接新任务,但处理完队列里已有的任务(调 shutdown() 进入)
STOP:不接新任务、不处理队列、中断正在执行的任务(调 shutdownNow() 进入)
TIDYING:所有任务已终止、工作线程数为 0,即将执行 terminated() 钩子
TERMINATED:terminated() 执行完毕,线程池彻底终止

shutdown vs shutdownNow

维度shutdown()shutdownNow()
进入状态SHUTDOWNSTOP
接新任务否(拒绝)否(拒绝)
队列里的任务执行完不执行(清空队列)
正在执行的任务让它执行完中断(interrupt)
返回值void返回未执行的任务列表 List<Runnable>
语义温和/优雅关闭强制关闭
ExecutorService pool = Executors.newFixedThreadPool(4);
// ... 提交任务

// 温和关闭:不接新任务,处理完已提交的(包括队列里的)再关
pool.shutdown();

// 强制关闭:立即停,返回没来得及执行的任务
List<Runnable> notExecuted = pool.shutdownNow();

// 优雅关闭的标准姿势:shutdown + 等待 + 超时后 shutdownNow
pool.shutdown();
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {  // 等待最多 60 秒
    pool.shutdownNow();   // 超时还没关完,强制关
}

⚠️ shutdown()shutdownNow() 都不会「阻塞等待线程池关闭完成」——它们只是「发起关闭」(改变状态),方法本身立即返回。要「等线程池真正关闭完」,得配合 awaitTermination(timeout)(阻塞等待直到线程池终止或超时)。所以「优雅关闭」的标准姿势是「shutdown() 发起关闭 → awaitTermination 等一段时间 → 若超时还没关完则 shutdownNow() 强制关」。别以为调了 shutdown 线程池就立即关了。

完整版教学

一、线程池为什么有状态

线程池是一个「有生命周期的资源」——它会经历「创建 → 运行 → 关闭」的过程,用状态来管理这个生命周期:

线程池的生命周期需要状态管理:
  创建后要能接任务、处理任务(RUNNING)
  要关闭时,有"温和关闭"(做完存量)和"强制关闭"(立即停)两种
  关闭是个过程(要等任务结束、线程退出),需要中间状态
  → 用 5 种状态精确描述"线程池现在处于生命周期的哪个阶段"

状态的作用:
  ① 控制行为(RUNNING 才接任务、SHUTDOWN 后拒绝新任务)
  ② 协调关闭过程(从发起关闭到彻底终止的各个阶段)

线程池用状态来「精确管理生命周期和行为」——不同状态下线程池的行为不同(能不能接新任务、处不处理队列、中不中断线程)。ThreadPoolExecutor 用一个原子整数 ctl 同时存「状态」和「工作线程数」(高 3 位存状态、低 29 位存线程数),用一个变量管两个信息,是巧妙的设计。理解「线程池有生命周期、用状态管理各阶段的行为、ctl 一个变量存状态+线程数」,就理解了状态的意义——它是线程池行为的「总控」。

二、五种状态的推进

线程池的 5 种状态按生命周期推进,理解每个状态的含义和转换:

RUNNING(初始):
  线程池创建后就是 RUNNING,能接受新任务、处理阻塞队列里的任务

RUNNING --shutdown()--> SHUTDOWN:
  不再接受新任务(新提交的走拒绝策略)
  但会继续处理"阻塞队列里已有的任务"(把存量做完)

RUNNING/SHUTDOWN --shutdownNow()--> STOP:
  不接新任务、不处理队列里的任务(清空队列)
  还会中断正在执行的任务(给工作线程发 interrupt)

SHUTDOWN/STOP --队列空且线程数为0--> TIDYING:
  所有任务都终止了、工作线程都退出了(线程数为 0)
  即将执行 terminated() 钩子方法

TIDYING --terminated()执行完--> TERMINATED:
  terminated() 钩子执行完,线程池彻底终止(生命周期结束)

状态推进的关键节点:RUNNING(能接能处理)→ shutdown 到 SHUTDOWN(不接但处理存量)或 shutdownNow 到 STOP(不接不处理还中断)→ 任务清空线程退出到 TIDYING → 钩子执行完到 TERMINATED。状态只能单向推进(不能回退),且值是递增的(RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED),便于用比较判断状态。TIDYING 和 TERMINATED 是「关闭的收尾阶段」(一般不用关心)。理解「五种状态单向推进、RUNNING 能接能处理、SHUTDOWN 处理存量、STOP 全停、TIDYING/TERMINATED 收尾」,就掌握了线程池的生命周期。

三、shutdown:温和关闭

shutdown() 是「温和/优雅关闭」——不再接受新任务,但把已经提交的任务(包括队列里排队的)都执行完,再关闭

pool.shutdown();
// 之后:
//   新提交的任务 → 被拒绝(走拒绝策略)
//   队列里已有的任务 → 继续执行完
//   正在执行的任务 → 让它正常执行完
//   等所有任务执行完 → 工作线程退出 → 线程池终止

shutdown() 的语义是「平滑收尾」——它承诺「已经接下的活(提交的任务)都会做完」,只是「不再接新活」。这适合「正常关闭」场景——如应用优雅停机时,让线程池把手头的任务做完再退出,不丢任务。它把线程池状态从 RUNNING 改为 SHUTDOWN,然后线程池会自然地处理完队列、退出线程。注意 shutdown() 立即返回(不阻塞等待任务做完),要等做完得配合 awaitTermination。理解「shutdown 温和关闭、不接新任务但做完存量、平滑收尾、适合优雅停机」,就掌握了它的语义——它是「善始善终」的关闭。

四、shutdownNow:强制关闭

shutdownNow() 是「强制/立即关闭」——不接新任务、清空队列(不执行排队的任务)、中断正在执行的任务,并返回「还没执行的任务列表」

List<Runnable> notExecuted = pool.shutdownNow();
// 之后:
//   新提交的任务 → 被拒绝
//   队列里已有的任务 → 不执行!清空队列,作为返回值返回
//   正在执行的任务 → 被中断(工作线程收到 interrupt)
//   → 线程池尽快终止

shutdownNow() 的语义是「尽快停止」——它「不管手头和排队的任务」,立即停:清空队列(把没执行的任务作为返回值返回,让你能记录/补偿)、中断正在执行的任务(发 interrupt,但任务是否响应中断取决于任务本身,见「线程中断」题)。它把状态改为 STOP。适合「紧急关闭」场景——如需要立即停止(出故障、强制退出)。注意shutdownNow() 是「尝试中断正在执行的任务」——如果任务不响应中断(如没检查中断标志的死循环),它可能还是停不下来。理解「shutdownNow 强制关闭、清空队列(返回未执行任务)、中断正在执行的、尽快停止、适合紧急关闭」,就掌握了它和 shutdown 的区别——它是「快刀斩乱麻」的关闭。

五、优雅关闭的标准姿势

实践中「优雅关闭线程池」有一个标准姿势——shutdown() + awaitTermination() + 超时后 shutdownNow()

void shutdownGracefully(ExecutorService pool) {
    pool.shutdown();   // 1. 发起温和关闭(不接新任务,做完存量)
    try {
        // 2. 等待一段时间让任务做完(awaitTermination 阻塞等待)
        if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
            // 3. 超时还没关完 → 强制关闭
            pool.shutdownNow();
            // 4. 再等一会儿确认
            if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
                log.error("线程池未能正常关闭");
            }
        }
    } catch (InterruptedException e) {
        pool.shutdownNow();   // 当前线程被中断,也强制关闭
        Thread.currentThread().interrupt();   // 恢复中断标志
    }
}

标准姿势的逻辑:shutdown() 先温和关闭(给存量任务做完的机会);② awaitTermination(timeout) 等一段时间(阻塞等待线程池终止);③ 如果超时还没关完,shutdownNow() 强制关闭(别无限等);④ 恢复中断标志。这个「先温和、给时间、超时强制」的模式兼顾了「尽量做完任务」和「不无限等待」。关键点:awaitTermination 才是「阻塞等待关闭完成」的方法(shutdown/shutdownNow 只是发起、立即返回)。Spring 的 @PreDestroy、应用优雅停机都用类似逻辑。理解「优雅关闭=shutdown+awaitTermination+超时shutdownNow、先温和给时间超时强制」,就掌握了线程池关闭的正确实践。

六、状态判断与相关方法

线程池提供了一些方法查询状态:

pool.isShutdown();      // 是否已调用 shutdown/shutdownNow(状态 >= SHUTDOWN)
pool.isTerminated();    // 是否已彻底终止(状态 = TERMINATED)
pool.isTerminating();   // 是否正在终止过程中(SHUTDOWN/STOP/TIDYING)
pool.awaitTermination(timeout, unit);  // 阻塞等待终止(返回是否在超时内终止)

// 注意区别:
//   isShutdown():调了 shutdown 就 true(不代表关完了)
//   isTerminated():真正关完了(所有任务结束、线程退出)才 true

要点:isShutdown() 表示「已发起关闭」(调了 shutdown/shutdownNow 就 true),不代表关完了;② isTerminated() 表示「真正关闭完成」(TERMINATED 状态);③ awaitTermination 阻塞等待关闭完成。别把 isShutdown() 当成「已关闭完成」——它只是「已发起关闭」。判断线程池是否真正关完用 isTerminated()。理解「isShutdown 是已发起关闭、isTerminated 是真正关完、awaitTermination 阻塞等待」,就能正确判断线程池状态——这几个方法容易混。

记忆钩子:「线程池 5 状态:RUNNING(能接能处理)→SHUTDOWN(shutdown 后不接但做完队列存量)/STOP(shutdownNow 后不接不处理还中断正在执行的)→TIDYING(任务终止线程为0)→TERMINATED(钩子执行完);shutdown 温和(做完存量再关、void)vs shutdownNow 强制(清空队列返回未执行任务+中断正在执行的);两者都只发起关闭立即返回,要等关完用 awaitTermination;优雅关闭=shutdown+awaitTermination+超时 shutdownNow」

七、常见误区与追问

  • 误区:shutdown 和 shutdownNow 都立即停止线程池。 shutdown 是温和关闭(不接新任务但做完队列里的存量任务再关),shutdownNow 才是强制(清空队列、中断正在执行的);且两者都只发起关闭、立即返回,不阻塞等待关完。
  • 误区:调了 shutdown 线程池就关闭了。 shutdown 只是「发起关闭」(改状态、立即返回),要等任务做完线程池才真正关闭;要阻塞等待关完用 awaitTermination,判断是否关完用 isTerminated(不是 isShutdown)。
  • 误区:shutdownNow 一定能立即停掉所有任务。 它「尝试」中断正在执行的任务(发 interrupt),但任务是否停下来取决于任务是否响应中断——不响应中断的任务(如没检查中断标志的死循环)可能还是停不下来。
  • 误区:isShutdown 返回 true 表示线程池已关闭完成。 isShutdown 只表示「已调用 shutdown/shutdownNow」(已发起关闭);真正关闭完成(TERMINATED)用 isTerminated 判断。
  • 追问:shutdown 和 shutdownNow 的区别? shutdown 进入 SHUTDOWN 状态(不接新任务、但处理完队列里已有的任务、让正在执行的执行完,返回 void);shutdownNow 进入 STOP 状态(不接新任务、清空队列不执行、中断正在执行的任务,返回队列里未执行的任务列表)。
  • 追问:怎么优雅关闭线程池? shutdown() 发起温和关闭 → awaitTermination(timeout) 阻塞等待一段时间让任务做完 → 若超时还没关完则 shutdownNow() 强制关闭 → 再等确认;兼顾「尽量做完任务」和「不无限等待」。
  • 追问:线程池有哪几种状态,怎么推进? RUNNING(能接能处理)→ shutdown 到 SHUTDOWN(不接做存量)或 shutdownNow 到 STOP(全停中断)→ 任务清空线程退出到 TIDYING → terminated() 钩子执行完到 TERMINATED;单向推进、值递增。

八、加强记忆

ThreadPoolExecutor5 种状态(用 ctl 一个原子整数同时存状态和线程数),按生命周期单向推进RUNNING(初始、能接新任务能处理队列)→ SHUTDOWNshutdown()不接新任务但处理完队列里已有的任务、让正在执行的执行完)或 STOPshutdownNow()不接、不处理队列、还中断正在执行的任务)→ TIDYING(任务全终止、线程数为 0)→ TERMINATEDterminated() 钩子执行完、彻底终止)。关闭的两个方法shutdown() 温和关闭(不接新任务但做完存量、返回 void、适合优雅停机);shutdownNow() 强制关闭(清空队列不执行、返回未执行的任务列表、中断正在执行的任务、适合紧急关闭,但任务不响应中断可能停不下来)。关键:两者都只「发起关闭」、立即返回,不阻塞等待关完——要等关闭完成用 awaitTermination(timeout)isShutdown 是已发起、isTerminated 才是真关完)。优雅关闭标准姿势shutdown()awaitTermination 等一段 → 超时则 shutdownNow() → 恢复中断标志。一句话「5 状态 RUNNING→SHUTDOWN/STOP→TIDYING→TERMINATED,shutdown 做完存量再关、shutdownNow 清队列返回未执行任务+中断正在执行的,都只发起关闭要 awaitTermination 等关完,优雅关闭 shutdown+await+超时 shutdownNow」。