CodeGym /课程 /JAVA 25 SELF /线程的状态与生命周期

线程的状态与生命周期

JAVA 25 SELF
第 51 级 , 课程 2
可用

1. Java 中线程的基本状态

在 Java 中,线程并不只是“已启动”或“已停止”。它有完整的生命周期,并且在每个阶段的行为都不同。理解这些阶段是编写稳定的多线程应用以及排查奇怪的“卡死”和“意外结束”的关键。

有哪些状态?

Java 定义了以下线程的主要状态(它们在枚举 Thread.State 中列出):

状态 说明
NEW
线程已创建,但尚未启动(new Thread(...),但未调用 start()
RUNNABLE
线程已就绪或正在运行
BLOCKED
线程在等待释放监视器(在 synchronized 块上被阻塞)
WAITING
线程在等待被其他线程“唤醒”(例如通过 Object.wait()
TIMED_WAITING
线程带超时地等待(例如 Thread.sleep(1000)wait(1000)join(1000)
TERMINATED
线程已结束执行

有趣的事实:
在早期的书籍和文章中,你可能会看到别的名字或略有不同的图示。但自 Java 5+ 起,这些状态被视为标准。

线程生命周期的可视化示意图

stateDiagram-v2
    [*] --> NEW
    NEW --> RUNNABLE: start()
    RUNNABLE --> BLOCKED: 尝试进入 synchronized,但监视器已被占用
    BLOCKED --> RUNNABLE: 监视器已释放
    RUNNABLE --> WAITING: wait(), join()
    RUNNABLE --> TIMED_WAITING: sleep(), wait(timeout), join(timeout)
    WAITING --> RUNNABLE: notify()/notifyAll(), join() 已完成
    TIMED_WAITING --> RUNNABLE: 超时已到 / notify()/notifyAll()
    RUNNABLE --> TERMINATED: run() 已结束
    WAITING --> TERMINATED: run() 已结束(少见)
    TIMED_WAITING --> TERMINATED: run() 已结束(少见)

2. 线程控制方法

休眠:Thread.sleep(long millis)

有时线程需要“睡一会儿”,以免打扰其他线程或等待某个事件。方法 Thread.sleep(ms) 会把线程在指定的毫秒数内置于

TIMED_WAITING
状态。

System.out.println("线程将休眠 2 秒...");
Thread.sleep(2000); // 休眠 2 秒
System.out.println("线程醒了!");
  • 休眠结束后,线程会回到状态
    RUNNABLE
    (就绪/可运行)。
  • 如果线程在睡眠期间被中断,会抛出 InterruptedException

等待另一个线程完成:join()

有时我们不仅要启动线程,还要等它完成。为此在 Java 中有方法 join()

Thread t = new Thread(() -> {
    System.out.println("正在工作...");
    try { Thread.sleep(1000); } catch (InterruptedException e) {}
    System.out.println("完成!");
});
t.start();
System.out.println("正在等待线程 t 完成...");
t.join(); // 当前线程(例如 main)等待 t
System.out.println("线程 t 已结束!");

这里,一旦调用 join(),主线程就开始等待。在此期间它处于状态

WAITING
,直到线程 t 执行完毕。还有带超时的变体—— join(long millis)。这种情况下线程仅等待限定时间,状态将是
TIMED_WAITING

中断线程:interrupt()

有时需要礼貌地请求线程结束工作,例如用户按下“取消”。为此使用方法 interrupt()

Thread t = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        // 工作中...
    }
    System.out.println("线程因中断而结束!");
});
t.start();
// ... 过一会儿:
t.interrupt(); // 给线程发信号: "该停止了"

需要理解的是,调用 interrupt() 并不会立刻杀死线程。它只是设置了一个标志位。线程本身需要不时通过 isInterrupted() 检查该标志并自行结束。如果线程此时正在睡眠(sleep())或等待(wait()),它不只是看到标志,还会立即收到异常 InterruptedException。这就是 Java 中“正确”的停止线程方式:程序不是强行打断线程,而是通知它该收尾了。

3. 状态迁移示例

下面通过示例看看线程如何在各个状态之间“旅行”。

示例 1:
NEW
RUNNABLE
TERMINATED

Thread t = new Thread(() -> System.out.println("你好!"));
System.out.println(t.getState()); // NEW
t.start();
System.out.println(t.getState()); // RUNNABLE (或 TERMINATED,如果线程非常快)
t.join();
System.out.println(t.getState()); // TERMINATED

示例 2:
RUNNABLE
TIMED_WAITING
RUNNABLE
TERMINATED

Thread t = new Thread(() -> {
    try {
        System.out.println("开始休眠...");
        Thread.sleep(1000); // TIMED_WAITING
        System.out.println("醒来了!");
    } catch (InterruptedException e) {
        System.out.println("线程被中断!");
    }
});
t.start();

示例 3:
RUNNABLE
WAITING
(使用 join()

Thread t1 = new Thread(() -> {
    try { Thread.sleep(500); } catch (InterruptedException ignored) {}
    System.out.println("t1 已结束");
});
Thread t2 = new Thread(() -> {
    try {
        t1.join(); // t2 等待 t1,处于 WAITING
        System.out.println("t2 已等到 t1");
    } catch (InterruptedException ignored) {}
});
t1.start();
t2.start();

示例 4:
BLOCKED

Object lock = new Object();

Thread t1 = new Thread(() -> {
    synchronized (lock) {
        try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
        System.out.println("t1 退出了 synchronized 块");
    }
});
Thread t2 = new Thread(() -> {
    synchronized (lock) {
        System.out.println("t2 进入了 synchronized");
    }
});
t1.start();
Thread.sleep(100); // 让 t1 先获取 lock
t2.start();
Thread.sleep(100); // 让 t2 尝试进入 synchronized
System.out.println("t2 的状态: " + t2.getState()); // BLOCKED

4. 如何获取线程状态?方法 isAlive()getState()

  • isAlive() —— 如果线程已启动且尚未结束则返回 true
    TERMINATED
    则为 false)。
  • getState() —— 返回线程的当前状态(来自枚举 Thread.State 的值)。
Thread t = new Thread(() -> {});
System.out.println(t.isAlive()); // false (NEW)
t.start();
System.out.println(t.isAlive()); // true (RUNNABLE/WAITING/...)
t.join();
System.out.println(t.isAlive()); // false (TERMINATED)

5. 为什么不能“直接杀死”线程以及其他实践建议

没有名为“kill”的方法!

Java 中没有可以“一条命令杀死”线程的方法。为什么?因为这不安全:如果线程持有某些资源(文件、连接、锁),强行终止可能会让系统处于不一致状态。

过时的方法:stop()suspend()resume()

在早期的 Java 版本中,曾有方法 stop()suspend()resume()。现在它们被标记为 @Deprecated,强烈不建议使用。为什么?

  • stop() 可能在任意时刻杀死线程,导致数据处于不一致状态。
  • suspend() 可能“冻结”持有锁的线程,从而导致整个程序卡死。
  • resume() 如果线程已结束,有时无法“解冻”它。

现代做法:
线程应通过检查中断标志(isInterrupted())或其他信号,自行、正确地结束。

最佳实践

  • 不要调用已标记为过时的方法。
  • 使用中断标志停止线程(interrupt() 并配合检查 isInterrupted())。
  • 调试时关注线程状态——这有助于发现卡顿和死锁。
  • 需要等待线程结束时,别忘了 join()

6. 使用线程生命周期时的常见错误

错误 1:重复启动线程。
在 Java 中,一个线程只能启动一次。若第二次调用 start()——将得到 IllegalThreadStateException。如果你需要重复执行任务——请创建新的 Thread 对象。

Thread t = new Thread(() -> {});
t.start();
t.start(); // 将抛出异常!

错误 2:混淆 run()start()
直接调用 run() 并不会在新线程中执行代码——它会在当前线程中执行(例如在 main)。只有 start() 才会真正启动新线程。

错误 3:不处理 InterruptedException
如果线程在睡眠或等待时被中断,会抛出 InterruptedException。如果忽略它,线程可能“永远睡下去”或意外结束。

错误 4:不检查线程状态。
有时程序之所以卡住,是因为一个线程在等待另一个已经结束或永远不会开始工作的线程。使用 getState()isAlive() 进行诊断。

错误 5:使用过时的线程控制方法。
方法 stop()suspend()resume() 是有害的。即便很想“快速修好”,也不要使用它们。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION