Java线程状态转换的详细过程

一、先明确:Java的6种线程状态(Thread.State)

这是状态转换的"基本单元",所有转换都围绕这6种状态进行:

scss 复制代码
1.NEW(新建):线程对象已创建(如new Thread()),但未调用start(),JVM未为其分配操作系统级线程资源。

2.RUNNABLE(可运行):调用start()后进入此状态,对应操作系统线程的"就绪"和"运行中"------线程要么在等待CPU调度(就绪),要么正在CPU上执行run()方法(运行中),JVM层面不区分这两个子状态。

3.BLOCKED(阻塞):仅因竞争synchronized锁失败而暂停,等待锁释放(不涉及Lock接口的锁,Lock锁竞争会进入WAITING/TIMED_WAITING)。

4.WAITING(无限等待):线程主动调用无参等待方法,释放CPU和持有的锁,必须依赖其他线程显式唤醒(否则永久等待)。

5.TIMED_WAITING(计时等待):线程调用带超时参数的等待方法,释放资源后仅等待指定时间,超时后自动唤醒,也可被提前唤醒。

6.TERMINATED(终止):线程的run()方法执行完毕,或因未捕获异常崩溃,生命周期彻底结束。

线程状态流程图

二、完整状态转换流程(带触发条件+场景)

线程从创建到终止,会经历以下核心转换路径,不同路径对应不同业务场景:

1. 初始启动:NEW → RUNNABLE

  • 触发操作:调用线程对象的start()方法(注意:不能重复调用,否则抛IllegalThreadStateException)。

  • 底层逻辑:start()会向JVM注册线程,JVM向操作系统申请创建线程(如Linux的pthread_create),操作系统将线程加入"就绪队列",等待CPU调度。

  • 场景:

    scss 复制代码
      Thread t = new Thread(() -> { /* 任务逻辑 */ });
      System.out.println(t.getState()); // 输出 NEW
      t.start();
      System.out.println(t.getState()); // 输出 RUNNABLE(大概率,因CPU调度有延迟)

2. 可运行态内部切换:RUNNABLE(就绪)↔ RUNNABLE(运行中)

  • 触发操作:由操作系统的CPU调度算法控制,JVM不干预。

    scss 复制代码
      就绪→运行中:CPU空闲时,调度器从就绪队列选一个线程分配时间片,线程开始执行run()方法。
      运行中→就绪:线程的CPU时间片用完,或有更高优先级线程进入就绪队列,当前线程被抢占,回到就绪队列。
  • 特点:此过程是"隐式转换",无需代码触发,开发者无法通过Thread.State感知(始终显示为RUNNABLE)。

3. 锁竞争:RUNNABLE ↔ BLOCKED

仅针对synchronized锁的竞争,是"被动阻塞"(线程未主动放弃,因锁被占用而暂停)。

RUNNABLE → BLOCKED:

arduino 复制代码
触发:线程尝试进入synchronized代码块/方法,但锁已被其他线程持有。

逻辑:线程从CPU调度队列退出,进入该锁的"阻塞队列",等待锁释放。

BLOCKED → RUNNABLE:

arduino 复制代码
触发:持有synchronized锁的线程退出同步块,释放锁。

逻辑:JVM从该锁的阻塞队列中唤醒一个线程(公平/非公平取决于JVM实现),线程重新进入就绪队列,等待CPU调度。

场景:

scss 复制代码
Object lock = new Object();
Thread t1 = new Thread(() -> {
    synchronized (lock) { /* 持有锁执行10秒 */ }
});
Thread t2 = new Thread(() -> {
    System.out.println(t2.getState()); // 先 RUNNABLE
    synchronized (lock) { /* 竞争锁失败 */ } 
    System.out.println(t2.getState()); // 竞争时变为 BLOCKED
});
t1.start();
Thread.sleep(100); // 确保t1先持有锁
t2.start();

4. 主动等待(无限):RUNNABLE ↔ WAITING

线程主动调用无参等待方法,释放CPU和锁,必须由其他线程显式唤醒(否则"卡死")。

RUNNABLE → WAITING(3种核心触发方式):

scss 复制代码
1.线程持有synchronized锁时,调用lock.wait()(必须在同步块内,否则抛IllegalMonitorStateException)。

2.调用另一个线程的thread.join()(无参):等待目标线程执行完毕,若目标线程未结束,当前线程进入等待。

3.调用LockSupport.park()(无参):无需持有锁,直接暂停,需通过LockSupport.unpark(thread)唤醒。

WAITING → RUNNABLE(对应唤醒方式):

scss 复制代码
1.其他线程调用lock.notify()/notifyAll()(唤醒后需重新竞争锁,竞争成功才回到RUNNABLE)。

2.join()的目标线程执行完毕(自动唤醒)。

3.其他线程调用LockSupport.unpark(thread)(直接唤醒,无需竞争锁)。

场景(wait/notify):

scss 复制代码
Object lock = new Object();
Thread t1 = new Thread(() -> {
    synchronized (lock) {
        System.out.println(t1.getState()); // RUNNABLE
        lock.wait(); // 释放锁,进入 WAITING
        System.out.println(t1.getState()); // 被唤醒并重新获锁后,回到 RUNNABLE
    }
});
Thread t2 = new Thread(() -> {
    synchronized (lock) {
        lock.notify(); // 唤醒t1
    }
});
t1.start();
Thread.sleep(100);
System.out.println(t1.getState()); // 输出 WAITING
t2.start();

5. 主动等待(计时):RUNNABLE ↔ TIMED_WAITING

线程调用带超时参数的等待方法,释放资源后等待指定时间,超时后自动唤醒(也可被提前唤醒)。

RUNNABLE → TIMED_WAITING(5种核心触发方式):

arduino 复制代码
1.Thread.sleep(long ms):不释放锁,仅暂停指定时间(最常用,无需持有锁)。

2.持有synchronized锁时,调用lock.wait(long ms)。

3.调用thread.join(long ms):等待目标线程指定时间,超时后不再等。

4.LockSupport.parkNanos(long nanos)/parkUntil(long deadline):带超时的暂停。

5.线程池中的线程等待任务(如ThreadPoolExecutor的awaitTermination(long, TimeUnit))。

TIMED_WAITING → RUNNABLE(2种唤醒方式):

scss 复制代码
1.等待时间到期(自动唤醒)。

2.被其他线程显式唤醒(如notify()/unpark(),与WAITING的唤醒逻辑一致)。

场景(sleep):

scss 复制代码
Thread t = new Thread(() -> {
    System.out.println(t.getState()); // RUNNABLE
    try {
        Thread.sleep(1000); // 进入 TIMED_WAITING
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
    System.out.println(t.getState()); // 超时后回到 RUNNABLE
});
t.start();
Thread.sleep(100);
System.out.println(t.getState()); // 输出 TIMED_WAITING

6. 最终终止:RUNNABLE → TERMINATED

线程生命周期的终点,一旦进入此状态,无法再回到其他状态。

触发条件:

scss 复制代码
1.线程的run()方法正常执行完毕(无异常)。

2.线程在run()方法中抛出未捕获的异常(如NullPointerException),导致线程崩溃。

3.其他线程调用thread.stop()(已废弃,会强制终止线程,可能导致资源泄漏)。

场景:

scss 复制代码
Thread t = new Thread(() -> {
    // 任务执行1秒后结束
    try { Thread.sleep(1000); } catch (InterruptedException e) {}
});
t.start();
Thread.sleep(2000); // 等待t执行完毕
System.out.println(t.getState()); // 输出 TERMINATED

三、关键注意点

1.BLOCKED vs WAITING/TIMED_WAITING:

scss 复制代码
BLOCKED是"被动等锁"(仅因synchronized锁竞争),不释放已持有的锁;

WAITING/TIMED_WAITING是"主动等待",会释放已持有的锁(sleep()除外,不释放锁)。

2.唤醒后的锁竞争:

scss 复制代码
由wait()唤醒的线程,必须重新竞争synchronized锁,竞争成功才会从WAITING/TIMED_WAITING进入RUNNABLE,否则会进入BLOCKED状态。

3.中断(interrupt())的影响:

arduino 复制代码
若线程处于WAITING/TIMED_WAITING状态时被中断(其他线程调用thread.interrupt()),会抛出InterruptedException,并清除中断标志,线程从等待状态回到RUNNABLE(需处理异常)。

BLOCKED状态的线程被中断,不会抛出异常,仅设置中断标志,状态仍为BLOCKED。

通过以上流程,可清晰理解线程在不同场景下的状态变化,以及代码操作对状态的影响。

相关推荐
尚学教辅学习资料2 小时前
基于Spring Boot的家政服务管理系统+论文示例参考
java·spring boot·后端·java毕设
Java水解2 小时前
从 “Hello AI” 到企业级应用:Spring AI 如何重塑 Java 生态的 AI 开发
后端·spring
平平无奇的开发仔2 小时前
Spring Boot 注解方式如何扫描并注册 BeanDefinition?
后端
用户6120414922132 小时前
C语言做的停车场管理系统
c语言·后端·敏捷开发
掘金者阿豪2 小时前
金仓数据库与Java整合Activiti工作流实战:从兼容性到SpringBoot配置
后端
郭京京2 小时前
go框架 Gin(上)
后端
ikun2 小时前
geotools28.6异常 java.lang.NoSuchFieldError: JAVA_9
后端
绝无仅有3 小时前
大厂Redis高级面试题与答案
后端·面试·github
Java进阶笔记3 小时前
JVM默认栈大小
java·jvm·后端