首页 / Java 入门教程 / 线程生命周期与状态

Java 入门教程

线程生命周期与状态

本教程共 100 篇 · 第 95 篇 · 更新于 2026-08-05 · 约 5 分钟阅读

JavaJava 入门教程线程状态生命周期Thread.Stateinterrupt

95. 线程生命周期与状态

本节目标:搞清线程的六种状态和它们之间的转换关系,能看懂线程卡在哪一步。

为什么要了解线程状态

线上服务卡死不响应,通常第一步就是打线程栈(jstack),看每个线程处于什么状态。

一堆线程停在 BLOCKED,说明有锁竞争。一堆停在 WAITING,说明在等某个永远不来的通知。看懂状态,才能定位问题。

Java 用枚举 Thread.State 定义了六种状态,它们是 JVM 层面的划分,和操作系统的线程状态不完全一样。

六种状态一览

状态含义
NEW已创建,还没调用 start()
RUNNABLE可运行,包括正在跑和等 CPU
BLOCKED等待获取 synchronized
WAITING无限期等待其他线程唤醒
TIMED_WAITING限时等待,超时自动醒
TERMINATED已结束

下面逐个说清楚。

NEW:出生但没启动

new Thread(...) 之后、start() 之前,线程处于 NEW

这时候它只是一个普通 Java 对象,操作系统还不知道它的存在,不会分配任何 CPU 时间。

RUNNABLE:可以跑

调用 start() 后进入 RUNNABLE

这里有个 Java 特有的设计:它把「就绪」和「运行中」合并成了一个状态

操作系统层面,线程有 Ready(等 CPU 调度)和 Running(正在执行)之分。但 Java 认为这个区别对程序员没有意义——你无法控制调度器什么时候切给你,所以干脆合并。

还有一点容易误解:线程等待 I/O 时也是 RUNNABLE。比如卡在 socket.read() 上等网络数据,JVM 依然报告 RUNNABLE,因为在 JVM 看来它没被 Java 层面的机制挂起。

BLOCKED:等锁

线程想进入 synchronized 代码块,但锁被别人占着,就进入 BLOCKED

这个状态只由 synchronized 产生。用 ReentrantLock 等待锁时,状态是 WAITINGTIMED_WAITING,不是 BLOCKED

锁一释放,线程会去争抢,抢到就回到 RUNNABLE

WAITING:无限期等待

调用下面这些方法会进入 WAITING

  • Object.wait()(不带超时)
  • Thread.join()(不带超时)
  • LockSupport.park()

关键词是无限期。线程会一直等下去,直到别的线程明确唤醒它(notify()notifyAll()、目标线程结束、unpark())。

没人唤醒,就永远醒不过来。很多「程序假死」就是这么来的。

TIMED_WAITING:限时等待

带超时参数的版本会进入 TIMED_WAITING

  • Thread.sleep(long)
  • Object.wait(long)
  • Thread.join(long)
  • LockSupport.parkNanos()

时间一到自动回到 RUNNABLE,不需要别人唤醒。相比 WAITING,它安全得多,因为有兜底。

Tip

设计并发逻辑时尽量用带超时的版本。多等几秒总比永久卡死强,还能在超时分支里打日志,方便排查。

TERMINATED:结束

run() 方法正常执行完,或者抛出未捕获异常退出,线程进入 TERMINATED

一旦终止就是终点,无法回到任何其他状态。想再执行一次,只能新建线程对象。

用代码观察状态

public class StateWatch {
    private static final Object LOCK = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            try {
                Thread.sleep(300);          // TIMED_WAITING
                synchronized (LOCK) {
                    LOCK.wait();            // WAITING
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "观察对象");

        System.out.println("start 前:" + t.getState());

        t.start();
        Thread.sleep(50);
        System.out.println("sleep 中:" + t.getState());

        Thread.sleep(500);
        System.out.println("wait 中:" + t.getState());

        synchronized (LOCK) {
            LOCK.notify();                  // 唤醒
        }
        t.join();
        System.out.println("结束后:" + t.getState());
    }
}
```text

输出:

```text
start 前:NEW
sleep 中:TIMED_WAITING
wait 中:WAITING
结束后:TERMINATED

想看到 BLOCKED,需要两个线程抢同一把锁:

public class BlockedDemo {
    private static final Object LOCK = new Object();

    public static void main(String[] args) throws InterruptedException {
        Runnable hold = () -> {
            synchronized (LOCK) {
                try {
                    Thread.sleep(2000); // 抱着锁不放
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        };

        Thread t1 = new Thread(hold, "持锁者");
        Thread t2 = new Thread(hold, "等锁者");

        t1.start();
        Thread.sleep(100);
        t2.start();
        Thread.sleep(100);

        System.out.println("t1 状态:" + t1.getState());
        System.out.println("t2 状态:" + t2.getState());
    }
}
```text

输出:

```text
t1 状态:TIMED_WAITING
t2 状态:BLOCKED

t1 拿着锁在睡觉,所以是 TIMED_WAITING;t2 进不去同步块,卡在 BLOCKED

BLOCKED 和 WAITING 的区别

初学者最容易混这两个。

BLOCKED被动的:我想要锁,但拿不到,只能排队。锁一空出来我就能继续,不需要谁通知我。

WAITING主动的:我自己调用 wait() 让出执行权,明确说「叫我起来我才走」。没人喊我就一直睡。

一个是抢不到资源,一个是主动放弃资源,性质完全不同。

中断机制

线程跑到一半想让它停下来,怎么办?

Thread 有过 stop() 方法,但早就被废弃了——它会强行终止线程,可能让共享数据停在半改不改的状态,非常危险。

Java 采用的是协作式中断:一个线程只能给另一个线程「发个信号」,对方看到信号后自己决定怎么收场。

public class InterruptDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread worker = new Thread(() -> {
            int count = 0;
            while (!Thread.currentThread().isInterrupted()) {
                count++;
                if (count % 100_000_000 == 0) {
                    System.out.println("已处理 " + count);
                }
            }
            System.out.println("收到中断信号,安全退出");
        }, "可中断任务");

        worker.start();
        Thread.sleep(500);
        worker.interrupt();   // 发信号,不是强杀
        worker.join();
        System.out.println("main 结束");
    }
}
```java

`interrupt()` 只是把目标线程的中断标志位设成 `true`。循环里用 `isInterrupted()` 检查这个标志,主动退出。

如果线程正卡在 `sleep()`、`wait()`、`join()` 上,情况不一样:这些方法会立刻抛出 `InterruptedException`,**同时把中断标志清零**

```java
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    Thread.currentThread().interrupt(); // 手动恢复标志
    return;                             // 然后退出
}
Warning

捕获 InterruptedException 后千万别只写一句 e.printStackTrace() 就完事。标志已经被清掉了,外层循环检查不到中断,线程会继续跑下去,中断请求就丢了。要么恢复标志,要么直接退出。

状态转换总览

把前面的内容串起来:

NEW --start()--> RUNNABLE
RUNNABLE --抢不到 synchronized 锁--> BLOCKED --拿到锁--> RUNNABLE
RUNNABLE --wait()/join()--> WAITING --notify()/目标结束--> RUNNABLE
RUNNABLE --sleep(n)/wait(n)--> TIMED_WAITING --超时--> RUNNABLE
RUNNABLE --run() 执行完--> TERMINATED
```bash

所有状态都必须经过 `RUNNABLE` 中转,不存在 `BLOCKED` 直接变 `WAITING` 这种跳转。

## 状态只能参考,不能依赖

`getState()` 拿到的是**某一瞬间的快照**。

你刚读到 `RUNNABLE`,下一纳秒线程可能已经变成 `TERMINATED` 了。所以绝不能写这种代码:

```java
// 错误示范
if (t.getState() == Thread.State.RUNNABLE) {
    // 到这一行时状态可能早就变了
}

getState() 的正确用途只有一个:调试和监控。查问题时打出来看看线程卡在哪,或者做个监控面板展示线程池状况。

真正的线程协调,要靠 join()、锁、CountDownLatch 这些同步工具,它们的语义是明确且可靠的。

本节小结

  • 六种状态:NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED
  • Java 的 RUNNABLE 合并了就绪和运行,等 I/O 时也算 RUNNABLE
  • BLOCKED 只由 synchronized 竞争产生,是被动排队。
  • WAITING 是主动放弃执行权,必须靠别人唤醒。
  • TIMED_WAITING 有超时兜底,比 WAITING 更安全。
  • TERMINATED 不可逆,线程无法重启。
  • 停止线程用 interrupt() 协作式中断,stop() 已废弃。
  • 捕获 InterruptedException 后要恢复中断标志或直接退出。