Java线程状态转换的详细过程
作者:似水流年流不尽思念
Java线程状态转换是Java多线程编程中的关键概念,对于理解和优化并发程序至关重要,Java线程在其生命周期中经历多种状态,这些状态之间的转换是由线程调度器根据特定的策略来决定的,以下是对Java线程状态转换的详细说明,需要的朋友可以参考下
一、先明确:Java的6种线程状态(Thread.State)
这是状态转换的“基本单元”,所有转换都围绕这6种状态进行:
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调度。
- 场景:
Thread t = new Thread(() -> { /* 任务逻辑 */ }); System.out.println(t.getState()); // 输出 NEW t.start(); System.out.println(t.getState()); // 输出 RUNNABLE(大概率,因CPU调度有延迟)
2. 可运行态内部切换:RUNNABLE(就绪)↔ RUNNABLE(运行中)
触发操作:由操作系统的CPU调度算法控制,JVM不干预。
就绪→运行中:CPU空闲时,调度器从就绪队列选一个线程分配时间片,线程开始执行run()方法。 运行中→就绪:线程的CPU时间片用完,或有更高优先级线程进入就绪队列,当前线程被抢占,回到就绪队列。
特点:此过程是“隐式转换”,无需代码触发,开发者无法通过Thread.State感知(始终显示为RUNNABLE)。
3. 锁竞争:RUNNABLE ↔ BLOCKED
仅针对synchronized锁的竞争,是“被动阻塞”(线程未主动放弃,因锁被占用而暂停)。
RUNNABLE → BLOCKED:
触发:线程尝试进入synchronized代码块/方法,但锁已被其他线程持有。 逻辑:线程从CPU调度队列退出,进入该锁的“阻塞队列”,等待锁释放。
BLOCKED → RUNNABLE:
触发:持有synchronized锁的线程退出同步块,释放锁。 逻辑:JVM从该锁的阻塞队列中唤醒一个线程(公平/非公平取决于JVM实现),线程重新进入就绪队列,等待CPU调度。
场景:
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种核心触发方式):
1.线程持有synchronized锁时,调用lock.wait()(必须在同步块内,否则抛IllegalMonitorStateException)。 2.调用另一个线程的thread.join()(无参):等待目标线程执行完毕,若目标线程未结束,当前线程进入等待。 3.调用LockSupport.park()(无参):无需持有锁,直接暂停,需通过LockSupport.unpark(thread)唤醒。
WAITING → RUNNABLE(对应唤醒方式):
1.其他线程调用lock.notify()/notifyAll()(唤醒后需重新竞争锁,竞争成功才回到RUNNABLE)。 2.join()的目标线程执行完毕(自动唤醒)。 3.其他线程调用LockSupport.unpark(thread)(直接唤醒,无需竞争锁)。
场景(wait/notify):
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种核心触发方式):
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种唤醒方式):
1.等待时间到期(自动唤醒)。 2.被其他线程显式唤醒(如notify()/unpark(),与WAITING的唤醒逻辑一致)。
场景(sleep):
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
线程生命周期的终点,一旦进入此状态,无法再回到其他状态。
触发条件:
1.线程的run()方法正常执行完毕(无异常)。 2.线程在run()方法中抛出未捕获的异常(如NullPointerException),导致线程崩溃。 3.其他线程调用thread.stop()(已废弃,会强制终止线程,可能导致资源泄漏)。
场景:
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:
BLOCKED是“被动等锁”(仅因synchronized锁竞争),不释放已持有的锁; WAITING/TIMED_WAITING是“主动等待”,会释放已持有的锁(sleep()除外,不释放锁)。
2.唤醒后的锁竞争:
由wait()唤醒的线程,必须重新竞争synchronized锁,竞争成功才会从WAITING/TIMED_WAITING进入RUNNABLE,否则会进入BLOCKED状态。
3.中断(interrupt())的影响:
若线程处于WAITING/TIMED_WAITING状态时被中断(其他线程调用thread.interrupt()),会抛出InterruptedException,并清除中断标志,线程从等待状态回到RUNNABLE(需处理异常)。 BLOCKED状态的线程被中断,不会抛出异常,仅设置中断标志,状态仍为BLOCKED。
通过以上流程,可清晰理解线程在不同场景下的状态变化,以及代码操作对状态的影响。
以上就是Java线程状态转换的详细过程的详细内容,更多关于Java线程状态转换的资料请关注脚本之家其它相关文章!