Java | 线程 - 生命周期

0 阅读12分钟

生命周期

1 整体生命周期状态图

经典图:清晰区分 NEW → RUNNABLE(就绪+运行)→ BLOCKED / WAITING / TIMED_WAITING → TERMINATED,并标注了所有进入方式。

image-20260227091339104

五状态经典图:新建 → 就绪 → 运行 → 阻塞 → 死亡。

Untitled-2025-12-11-2114.excalidraw

超级详细版(带文字说明):把就绪细分为“等待CPU”和“运行中”,阻塞分为各种场景,非常适合深入理解。

stateDiagram-v2
    [*] --> NEW : new Thread()
    NEW --> RUNNABLE : start()
    RUNNABLE --> BLOCKED : 等待 synchronized 锁
    BLOCKED --> RUNNABLE : 获取到锁
    RUNNABLE --> WAITING : wait() / join()
    RUNNABLE --> TIMED_WAITING : sleep() / wait(time)
    WAITING --> RUNNABLE : notify() / interrupt()
    TIMED_WAITING --> RUNNABLE : 时间到 / interrupt()
    RUNNABLE --> TERMINATED : run() 执行完毕
    BLOCKED --> TERMINATED : 极少见,通常先变 RUNNABLE
    WAITING --> TERMINATED : 极少见
    TIMED_WAITING --> TERMINATED : 极少见

Untitled-2026-02-27-1528

线程生命周期核心规则:

  1. 状态单向不可逆
    一旦线程调用 start() 进入 RUNNABLE,就永远回不到 NEW;一旦到达 TERMINATED,就再也无法进入任何其他状态(终态,不可复活)。

  2. 同一个线程对象只能 start() 一次
    第二次调用 start() 会直接抛出 IllegalThreadStateException(一生只能启动一次)。

  3. RUNNABLE 是“万能中转站”
    几乎所有状态转换都要经过 RUNNABLE(包括就绪+运行两种OS层面状态,JVM不细分);NEW → RUNNABLE → 各种阻塞/等待 → RUNNABLE → TERMINATED 是最典型路径。

  4. 阻塞与等待的唤醒依赖外部事件

    • BLOCKED(synchronized锁竞争)→ 自动靠获得锁转为RUNNABLE
    • WAITING → 必须被 notify/notifyAllunpark被join的线程结束 唤醒
    • TIMED_WAITING → 除上述外,还可以靠时间到达自动唤醒
      程序员无法“强行把线程从等待中拽出来”,必须靠这些触发条件。
  5. 禁止使用 stop()、suspend()、resume()
    这几个方法早在 JDK 1.2 就标记为Deprecated,极不安全:

    • stop() 会直接杀死线程,可能导致资源不释放、对象处于不一致状态
    • suspend() 会让线程永久挂起而不释放锁,极易死锁
      现代推荐做法:用 interrupt() + 主动检查中断标志(isInterrupted() / Thread.interrupted())来协作式终止线程。

    image-20260227153550076

  6. interrupt() 只是“礼貌请求”中断,不是强制杀死

    • 仅对处于 sleep()、wait()、join()、park() 等可中断方法的线程会抛出 InterruptedException
    • 对普通运行代码无直接效果,必须由线程自己检查中断状态并主动退出 (这是“优雅关闭线程”的核心原则)
  7. run() 执行结束或未捕获异常 = 死亡
    线程的 TERMINATED 状态只能通过 run() 方法正常返回 或 抛出未捕获异常两种方式到达,没有其他途径。

2 各状态详细说明

总结:

新建start()就绪 → 获取CPU → 运行
运行

  • sleep/wait/join/park/synchronized拿不到锁阻塞
  • yield()/时间片结束就绪
  • run()结束/异常终止

阻塞时间到/notify/unpark/锁释放就绪

(1)新建状态(New)

  • 特征:线程对象已被 new 创建,但还未调用 start() 方法。同一个线程对象不能多次调用 start () 方法,否则会抛出IllegalThreadStateException异常。

    image-20260227153433914

  • 此时线程:还没有被 JVM 分配资源(栈空间、程序计数器等),本质上只是一个普通 Java 对象。

  • 能做什么:只能调用 start(),不能调用 run()(直接调用 run() 只是普通方法,不会启动新线程)。

  • 代码示例

Thread t = new Thread(() -> {
    System.out.println("我正在运行");
});   // ← 此时处于 New 状态

状态流转new Thread()新建t.start()就绪

(2)就绪状态(Runnable / Ready)

  • 特征:已调用 start(),线程已经准备好运行,等待操作系统分配 CPU 时间片。
  • 注意:Java 官方 Thread.State 中把就绪 + 运行合并为一个 RUNNABLE 状态,但国内通常把它们拆开讲解。
  • 此时线程:在就绪队列中排队,具备运行的一切条件,只差 CPU。
  • 代码示例
t.start();   // ← 调用后立即进入就绪状态

状态流转

  • 就绪 → 获得 CPU → 运行
  • 运行中被抢占 / yield() / 时间片用完 → 回到 就绪

(3)运行状态(Running)

  • 特征:线程已获得 CPU 时间片,正在真正执行 run() 方法中的代码。
  • 此时线程:是 CPU 的“宠儿”,正在跑业务逻辑。
  • 可能被打断的情况
    • 时间片用完(最常见)
    • 调用 Thread.yield()(主动让出)
    • 被更高优先级线程抢占
    • 执行到阻塞操作(如 sleep、wait、synchronized 拿不到锁)

状态流转

  • 运行 → 执行完毕或异常 → 终止
  • 运行 → 遇到阻塞操作 → 阻塞

(4)阻塞状态(Blocked / Waiting / Timed Waiting)

这是面试最爱深挖的部分。阻塞不是一个单一状态,而是三大类:

子状态英文触发方式(最常见)唤醒方式特点
BlockedBLOCKED竞争 synchronized 锁失败其他线程释放锁等待锁(monitor)
WaitingWAITINGobj.wait()thread.join()LockSupport.park()notify()/notifyAll()unpark()、join线程结束无限期等待
Timed WaitingTIMED_WAITINGThread.sleep()wait(timeout)join(timeout)parkNanos()时间到 或 被提前唤醒有超时时间

代码示例(三大阻塞):

// 1. BLOCKED(锁阻塞)
synchronized (obj) {
    // 如果其他线程已经拿到锁,这里就会进入 BLOCKED
}

// 2. WAITING(无限等待)
synchronized (obj) {
    obj.wait();        // 必须在 synchronized 中调用
}

// 3. TIMED_WAITING(限时等待)
Thread.sleep(2000);          // 最常见
obj.wait(2000);
t.join(5000);

重要区别记忆口诀

  • Blocked = 等锁(synchronized)
  • Waiting = 主动“喊暂停”(wait/park/join),需要别人叫醒
  • Timed Waiting = “喊暂停但带闹钟”(sleep/wait+timeout)

状态流转:所有阻塞 → 条件满足 → 回到 就绪

(5)终止状态(Terminated)

  • 特征:线程 run() 方法正常结束、抛出未捕获异常、或被 stop()(已废弃,不推荐)。
  • 此时线程:彻底死亡,资源被回收,不能再 start()(会抛 IllegalThreadStateException)。
  • 代码示例
// run() 执行完自动进入 Terminated
// 或线程内抛出 RuntimeException 未捕获

状态流转不可逆!终止后无法回到任何其他状态。


3 代码演示

package Thread_Lifecycle;

/**
 * 线程生命周期状态演示类
 * 展示Java线程的6种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED
 */
public class ThreadStateDemo {
    // 定义一个共享对象锁,用于演示线程同步和阻塞状态
    private static final Object lock = new Object();

    /**
     * 主方法 - 演示各种线程状态
     * @param args 命令行参数
     * @throws InterruptedException 当线程被中断时抛出
     */
    public static void main(String[] args) throws InterruptedException {
        // 1. NEW 状态演示
        // 创建线程对象但未调用start()方法,此时线程处于NEW状态
        Thread t1 = new Thread(() -> {
            // 线程执行体 - 实际工作代码
            System.out.println("t1 进入运行状态...");
            try {
                // 此时线程进入TIMED_WAITING状态
                Thread.sleep(200);
            } catch (InterruptedException e) {
                // 处理中断异常情况
                System.out.println("t1 被中断");
            }
            // 线程执行完毕,即将进入TERMINATED状态
            System.out.println("t1 执行结束 → TERMINATED");
        }, "t1");  // 给线程命名,便于识别和调试

        // 获取并打印线程创建后的初始状态(应该是NEW)
        System.out.println("t1 创建后状态: " + t1.getState());

        // 2. RUNNABLE状态演示
        // 调用start()方法启动线程,线程进入RUNNABLE状态(就绪态)
        // JVM调度器会很快将其分配CPU时间片开始执行
        t1.start();
        // 主线程休眠100毫秒,给t1线程时间开始执行
        Thread.sleep(100);
        // 此时t1应该已经在执行sleep()方法,处于TIMED_WAITING状态
        System.out.println("t1 start 后状态: " + t1.getState());

        // 3. BLOCKED状态演示(锁竞争场景)
        // 创建第一个线程t2,它会获取lock对象的监视器锁
        Thread t2 = new Thread(() -> {
            // 使用synchronized关键字获取lock对象的内置锁
            synchronized (lock) {
                System.out.println("t2 拿到锁,正在执行...");
                try {
                    // 持有锁的情况下休眠0.3秒,期间其他线程无法获取该锁
                    Thread.sleep(300);
                } catch (InterruptedException ignored) {
                    // 忽略中断异常
                }
            }
            // synchronized块结束时自动释放锁
        }, "t2");

        // 创建第二个线程t3,它也会尝试获取同一个lock对象的锁
        Thread t3 = new Thread(() -> {
            // 当t2持有锁时,t3执行到这里会被阻塞
            // 因为无法获取已被t2占用的锁,进入BLOCKED状态
            synchronized (lock) {
                System.out.println("t3 拿到锁");
            }
        }, "t3");

        // 启动t2线程,让它先获取锁
        t2.start();
        // 等待200毫秒确保t2已经获取到锁
        Thread.sleep(200);
        // 启动t3线程,此时t3会因为锁被占用而进入BLOCKED状态
        t3.start();
        // 等待300毫秒让t3充分体验BLOCKED状态
        Thread.sleep(300);
        // 打印t3的状态,应该显示为BLOCKED
        System.out.println("t3 状态(应为 BLOCKED): " + t3.getState());

        // 4. WAITING状态演示(使用Object.wait()方法)
        // 创建t4线程用于演示无限期等待状态
        Thread t4 = new Thread(() -> {
            // 获取lock对象的锁
            synchronized (lock) {
                try {
                    System.out.println("t4 进入 wait → WAITING");
                    // 调用wait()方法释放锁并进入无限期等待状态
                    // 直到其他线程调用notify()/notifyAll()才会唤醒
                    lock.wait();
                } catch (InterruptedException e) {
                    // 处理可能的中断异常
                    e.printStackTrace();
                }
            }
        }, "t4");

        // 启动t4线程
        t4.start();
        // 等待500毫秒确保t4进入WAITING状态
        Thread.sleep(500);
        // 打印t4状态,应该显示为WAITING
        System.out.println("t4 状态(应为 WAITING): " + t4.getState());

        // 唤醒处于WAITING状态的t4线程
        // 需要先获取相同的锁对象才能调用notify()
        synchronized (lock) {
            // 唤醒在lock对象上等待的单个线程(t4)
            lock.notify();
        }

        // 5. TIMED_WAITING状态演示(使用带超时的join方法)
        // 创建t5线程用于演示定时等待
        Thread t5 = new Thread(() -> {
            try {
                // t5线程简单地休眠0.5秒
                Thread.sleep(500);
            } catch (InterruptedException ignored) {
                // 忽略中断异常
            }
        }, "t5");
        // 启动t5线程
        t5.start();

        // 获取当前主线程的引用
        Thread main = Thread.currentThread();
        // 创建一个辅助线程来演示主线程的TIMED_WAITING状态
        new Thread(() -> {
            try {
                // 调用t5.join(2000)使当前线程(辅助线程)等待t5线程最多0.2秒
                // 如果2秒内t5没有结束,则继续执行
                // 调用线程在此期间进入TIMED_WAITING状态
                t5.join(500);
            } catch (InterruptedException e) {
                // 处理中断异常
                e.printStackTrace();
            }
        }).start();

        // 等待500毫秒确保辅助线程已经开始等待
        Thread.sleep(500);
        // 打印主线程状态,应该显示为TIMED_WAITING
        System.out.println("主线程在 join(2000) 时状态: " + main.getState());

        // 等待所有演示线程完成执行
        // join()方法使当前线程(main线程)等待指定线程执行完毕
        t1.join();
        t2.join();
        t3.join();
        t4.join();
        t5.join();
        // 打印t1线程的最终状态,应该是TERMINATED
        System.out.println("t1 最终状态: " + t1.getState());
    }
}

SNAP_2026-02-27_16-47-57

4 wait()sleep() 的 8 大区别(面试必问)

口诀总结:“wait会放锁睡大觉,别人叫醒才起床;sleep抱锁睡小觉,时间一到自己醒。”

序号比较项wait()sleep()记忆口诀
1所属类ObjectThreadwait是对象的,sleep是线程的
2使用环境必须在 synchronized 块/方法内无需 synchronizedwait必须“有锁”
3是否释放锁释放当前持有的锁不释放锁wait会放锁,sleep死抓着
4唤醒方式notify/notifyAll 或 interrupt时间到自动醒,或 interruptwait靠别人,sleep靠闹钟
5是否可超时有重载 wait(long timeout)有 sleep(long millis)两者都可带时间
6中断响应抛 InterruptedException 并清除中断标志抛 InterruptedException 并清除中断标志行为一致
7所属状态(最典型)WAITING 或 TIMED_WAITINGTIMED_WAITINGsleep一定是限时等待
8假唤醒(spurious wakeup)可能发生,必须放在 while 循环中判断条件不存在假唤醒wait要防“骗醒”

5 Thread.State 枚举源码分析

理解线程状态流转对于排查死锁和性能问题至关重要。Java 线程主要有以下 6 种状态(基于 Thread.State 枚举):

  1. NEW (新建):创建了线程对象,但未调用 start()
  2. RUNNABLE (可运行):调用了 start(),正在 JVM 中等待 CPU 时间片,或者正在运行中。
  3. BLOCKED (阻塞):等待获取监视器锁(synchronized 锁)。
  4. WAITING (无限等待):调用 wait(), join(), LockSupport.park(),需被其他线程唤醒。
  5. TIMED_WAITING (计时等待):调用 sleep(time), wait(time),时间到了自动唤醒。
  6. TERMINATED (终止):线程执行结束。
public class Thread implements Runnable {
    public enum State {
        /**线程尚未启动。还未调用 start()。*/
        NEW,

        /**线程在 JVM 中执行中(包括就绪 + 真正运行 + OS 调度相关等待)。
         * 这是最宽泛的状态。*/
        RUNNABLE,

        /** 线程在等待 monitor 锁(synchronized 块/方法)。
         * 典型场景:竞争 synchronized 锁失败。*/
        BLOCKED,

        /** 线程在无限期等待另一个线程执行特定动作。
         * 典型:wait()、join()、LockSupport.park()*/
        WAITING,

        /** 线程在有限时间内等待另一个线程执行特定动作。
         * 典型:sleep()、wait(timeout)、join(timeout)、parkNanos、parkUntil*/
        TIMED_WAITING,

        /**线程已终止(run()正常结束或异常结束)。
         * 不可再 start()。*/
        TERMINATED;
    }
}

image-20260227165355165

6 Java线程状态与操作系统内核线程的对应关系

Java 线程状态操作系统层面(典型对应)说明
NEW无(尚未创建内核线程)Java 层只是对象,start() 后才真正创建 pthread / kernel thread
RUNNABLEReady / Running就绪队列中等待调度 或 正在 CPU 上执行
BLOCKEDWaiting (for mutex / monitor)等待互斥锁(synchronized 底层是 monitorenter)
WAITINGWaiting / Suspended / Parked等待条件变量(wait/notify 底层是条件变量)、futex wait、park 等
TIMED_WAITINGWaiting with timeout同上,但设置了超时(sleep、wait(timeout)、pthread_cond_timedwait 等)
TERMINATEDZombie → Dead线程结束,等待被 join 或系统回收资源

总结对应规律:

  • Java 故意简化了状态(RUNNABLE 合并就绪+运行),隐藏了操作系统细节

  • BLOCKED 是最“重”的阻塞(与锁相关),其他都是“轻量级”等待

  • 现代 JVM(HotSpot)底层大量使用 park/unpark(基于 futex / semaphore 等),而不是传统条件变量

    Grok给出:

    image-20260228120710977image-20260228120744125

image-20260228113610377

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadStateDemo {

    private static final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws Exception {
        // 1. NEW 状态(创建但未start)
        Thread tNew = new Thread(() -> {}, "t-new");
        printThreadInfo(tNew, "刚new出来");

        // 2. RUNNABLE(正在计算或ready)
        Thread tRunnable = new Thread(() -> {
            long start = System.currentTimeMillis();
            while (true) {
                if (System.currentTimeMillis() - start > 20_000) break;
                Math.sin(Math.random()); // 占用CPU
            }
        }, "t-runnable");
        tRunnable.start();
        Thread.sleep(800); // 让它跑起来
        printThreadInfo(tRunnable, "RUNNABLE - 忙循环");

        // 3. BLOCKED(synchronized争锁)
        Thread tBlocked = new Thread(() -> {
            synchronized (ThreadStateDemo.class) {
                LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(30));
            }
        }, "t-blocked");

        // 先让主线程持有锁
        synchronized (ThreadStateDemo.class) {
            tBlocked.start();
            Thread.sleep(800);
            printThreadInfo(tBlocked, "BLOCKED - 争synchronized锁");
        }

        // 4. WAITING(Object.wait() 无超时)
        Thread tWaiting = new Thread(() -> {
            synchronized (ThreadStateDemo.class) {
                try {
                    ThreadStateDemo.class.wait(); // 会一直等
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }, "t-waiting");
        tWaiting.start();
        Thread.sleep(800);
        printThreadInfo(tWaiting, "WAITING - Object.wait()");

        // 5. TIMED_WAITING(几种常见形式)
        Thread tTimed = new Thread(() -> {
            try {
                Thread.sleep(15000);  // 最简单的一种
                // 或者: LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(30));
                // 或者: synchronized后 wait(15000)
            } catch (Exception e) {
                Thread.currentThread().interrupt();
            }
        }, "t-timed-waiting");
        tTimed.start();
        Thread.sleep(800);
        printThreadInfo(tTimed, "TIMED_WAITING - Thread.sleep()");

        // 6. 让程序不要马上退出
        System.out.println("\n所有演示线程已启动。pid = " + ProcessHandle.current().pid());
        System.out.println("请在新终端运行以下命令观察(大约10秒内):");
        System.out.println("  jstack " + ProcessHandle.current().pid());
        System.out.println("  top -H -p " + ProcessHandle.current().pid());
        System.out.println("  ps -eLo pid,lwp,stat,comm | grep t-");
        System.out.println("  cat /proc/" + ProcessHandle.current().pid() + "/task/[LWP]/stat   # 看第3列状态字母");
        System.out.println("\n按回车退出...");
        System.in.read();
    }

    private static void printThreadInfo(Thread t, String msg) {
        System.out.printf("%-25s : %-10s  (java state)%n", msg, t.getState());
    }
}

image-20260228112014228

SNAP_2026-02-28_11-57-12