Java线程状态:
Java线程在其生命周期中会经历一系列的状态变化,这些状态反映了线程在不同阶段的行为和能力。根据Java官方文档和JDK源码中java.lang.Thread.State
枚举的定义,Java线程主要有 NEW(新建)、RUNNABLE(可运行/运行中)、BLOCKED(阻塞)、WAITING(等待)、 TIMED_WAITING(超时等待)、 TERMINATED(终止)六个状态:
-
NEW(新建)
- 描述:当线程对象被创建但尚未调用其
start()
方法时,线程处于NEW状态。此时,线程对象已经存在,但尚未与操作系统内核线程关联,也没有开始执行。 - 行为:线程尚未开始执行任何代码,未分配任何系统资源。
- 描述:当线程对象被创建但尚未调用其
-
RUNNABLE(可运行/运行中)
- 描述:一旦调用了
start()
方法,线程进入RUNNABLE状态。此时,线程已被操作系统认可,具备了执行资格,可以被调度器分配CPU时间片执行。实际上,RUNNABLE状态包含了两种不同的子状态:- 就绪(Ready):线程已经准备好运行,等待CPU调度。线程在等待队列中,一旦获得CPU时间片,就立即转为运行状态。
- 运行(Running):线程正在使用CPU执行任务。这是线程真正执行代码的状态。
- 行为:线程可能在CPU上执行,也可能在就绪队列中等待执行机会。在操作系统层面,这两个子状态并不区分,统一视为RUNNABLE。
- 描述:一旦调用了
-
BLOCKED(阻塞)
- 描述:线程因等待进入一个同步块(如
synchronized
关键字修饰的代码块或方法)而暂时停止运行,当前线程必须等待其他线程释放锁才能继续执行。阻塞状态发生在线程试图获取一个由其他线程持有的锁时。 - 行为:线程暂停执行,不占用CPU,直到获得所需的锁。
- 描述:线程因等待进入一个同步块(如
-
WAITING(等待)
- 描述:线程处于一种无限期等待其他线程采取行动的状态。这通常发生在调用以下方法之一时:
Object.wait()
(不带超时参数)。Thread.join()
(不带超时参数)。LockSupport.park()
。
- 行为:线程不会消耗CPU资源,只有当其他线程显式唤醒(如通过
notify()
、notifyAll()
或LockSupport.unpark()
)或满足特定条件时,才会重新变为可运行状态。
- 描述:线程处于一种无限期等待其他线程采取行动的状态。这通常发生在调用以下方法之一时:
-
TIMED_WAITING(超时等待)
- 描述:类似于WAITING状态,但线程具有超时限制。当超时时间到达或收到唤醒信号时,线程将恢复到可运行状态。这种状态发生在调用以下方法之一时:
Object.wait(long timeout)
。Thread.sleep(long millis)
。Thread.join(long millis)
。LockSupport.parkNanos(long nanos)
。LockSupport.parkUntil(long deadline)
。
- 行为:线程暂停执行,不占用CPU,直到超时时间结束或被其他线程唤醒。
- 描述:类似于WAITING状态,但线程具有超时限制。当超时时间到达或收到唤醒信号时,线程将恢复到可运行状态。这种状态发生在调用以下方法之一时:
-
TERMINATED(终止)
-
描述:线程已完成其任务或因异常退出了run()方法。线程已经结束其生命周期,不能再被启动或执行任何操作。
-
行为:线程彻底结束,系统资源被回收。可以通过
isAlive()
方法判断线程是否处于TERMINATED状态。Java线程的生命周期是从创建(NEW) 开始,经过 启动(RUNNABLE)、可能的阻塞(BLOCKED)、等待(WAITING/TIMED_WAITING)状态,最终到达终止(TERMINATED) 状态。这些状态之间并非严格单向过渡,线程可能会在RUNNABLE、BLOCKED、WAITING/TIMED_WAITING之间多次切换,直到最终结束。理解线程状态有助于分析多线程程序的行为,调试并发问题,以及设计高效的并发控制策略。
-
下面是一个简单的Java线程生命周期示例代码:
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程启动");
try {
// 睡眠500毫秒
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程结束");
}
});
// 打印线程的初始状态
System.out.println("新建线程状态: " + thread.getState());
// 启动线程
thread.start();
// 打印运行后的状态,这里可能会看到不同的状态,取决于CPU的调度
// 通常会看到状态为NEW, TIMED_WAITING, RUNNABLE, TERMINATED
System.out.println("线程运行后状态: " + thread.getState());
try {
Thread.sleep(1000); // 等待线程执行结束
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程结束状态: " + thread.getState());
Java线程状态切换:
Java线程状态之间的切换是由特定的事件或操作触发的。以下是各状态之间切换的具体情况和触发因素:
NEW → RUNNABLE
- 触发操作:调用线程对象的
start()
方法。 - 切换过程:
start()
方法内部会调用JVM的底层API,请求操作系统创建一个内核线程,并将Java线程对象与之关联。此时,线程进入就绪队列,等待操作系统调度器分配CPU时间片。一旦获得CPU,线程开始执行其run()
方法,进入RUNNING子状态。
RUNNABLE → BLOCKED
- 触发事件:线程试图获取一个由其他线程持有的锁(如
synchronized
关键字修饰的代码块或方法、ReentrantLock
等)。 - 切换过程:线程暂停执行,放弃CPU,进入操作系统内核等待队列,等待锁的持有者释放锁。一旦锁被释放,线程重新进入就绪队列,等待调度执行。
RUNNABLE → WAITING
- 触发操作:线程主动调用
Object.wait()
(无超时参数)、Thread.join()
(无超时参数)或LockSupport.park()
方法。 - 切换过程:线程自愿放弃CPU,进入无限期等待状态,等待其他线程通过
notify()
、notifyAll()
或LockSupport.unpark()
方法唤醒。在等待期间,线程不消耗CPU资源,也不会被调度器选中执行。
RUNNABLE → TIMED_WAITING
- 触发操作:线程调用带有超时参数的方法,如
Object.wait(long timeout)
、Thread.sleep(long millis)
、Thread.join(long millis)
、LockSupport.parkNanos(long nanos)
或LockSupport.parkUntil(long deadline)
。 - 切换过程:线程进入定时等待状态,放弃CPU,等待指定时间结束或接收到唤醒信号。在等待期间同样不消耗CPU资源。超时时间结束后,线程自动转入就绪状态,等待调度执行;若在此之前被其他线程唤醒,也会提前回到就绪状态。
(BLOCKED/WAITING/TIMED_WAITING) → RUNNABLE
- 触发事件:
- BLOCKED:持有线程释放了线程正在等待的锁。
- WAITING:其他线程调用了与当前线程相关联的
Object.notify()
、Object.notifyAll()
或LockSupport.unpark()
方法。 - TIMED_WAITING:等待时间结束(超时)或接收到唤醒信号。
- 切换过程:线程从等待队列移除,重新进入就绪队列,等待调度器分配CPU时间片。一旦获得CPU,线程继续执行其
run()
方法。
RUNNABLE → TERMINATED
-
触发事件:线程的
run()
方法正常结束或抛出了未被捕获的异常。 -
切换过程:线程执行完毕,操作系统内核线程被销毁,Java线程对象的状态被设置为TERMINATED,且不可再被启动。此时,线程占用的系统资源被回收。
Java线程状态的切换主要由线程自身的方法调用、其他线程的操作(如释放锁、发送通知)以及系统的调度决策等因素驱动。这些状态之间的转换反映了线程在执行过程中对资源的竞争、协作以及生命周期的不同阶段。理解这些状态转换有助于分析多线程程序的行为,排查并发问题,以及优化线程的使用。