baeldung 2024-01-08
1. 引言
在本文中,我们将详细探讨 Java 中的一个核心概念——线程的生命周期。
我们会使用一张简明的示意图,并辅以实用的代码片段,帮助大家更好地理解线程在其执行过程中所经历的各种状态。
若要开始了解 Java 中的线程,可以先阅读这篇关于如何创建线程的文章。
2. Java 中的多线程
Java 语言中的多线程机制由 Thread(线程) 这一核心概念驱动。在线程的整个生命周期中,它会经历多种不同的状态:
线程的生命周期
3. Java 中线程的生命周期
java.lang.Thread 类中包含一个静态的 State 枚举类型,用于定义线程可能处于的各种状态。在任意时刻,线程只能处于以下六种状态之一:
- NEW(新建):已创建但尚未启动执行的线程。
- RUNNABLE(可运行):正在运行或已准备好运行,但正在等待系统分配资源。
- BLOCKED(阻塞):正在等待获取监视器锁,以便进入或重新进入某个同步代码块/方法。
- WAITING(等待):无限期地等待其他线程执行特定操作。
- TIMED_WAITING(计时等待):在指定时间内等待其他线程执行特定操作。
- TERMINATED(终止):已完成执行(正常结束或异常终止)。
以上所有状态均已在上图中体现;接下来我们将逐一详细说明。
3.1. NEW(新建)
一个 NEW 状态的线程(也称为“新生线程”)是指已经创建但尚未调用 start() 方法启动的线程。只有当我们调用其 start() 方法后,它才会离开此状态。
以下代码片段展示了一个处于 NEW 状态的新建线程:
Runnable runnable = new NewState();
Thread t = new Thread(runnable);
System.out.println(t.getState());
由于我们尚未启动该线程,因此 t.getState() 的输出为:
NEW
3.2. RUNNABLE(可运行)
当我们创建一个新线程并调用其 start() 方法后,线程会从 NEW 状态转为 RUNNABLE 状态。处于该状态的线程要么正在运行,要么已准备好运行,只是在等待系统分配 CPU 资源。
在多线程环境中,JVM 的 线程调度器(Thread-Scheduler) 会为每个线程分配固定的时间片。线程运行一段时间后,会主动让出控制权,交由其他处于 RUNNABLE 状态的线程执行。
例如,在前面的代码中添加 t.start() 并查看其当前状态:
Runnable runnable = new NewState();
Thread t = new Thread(runnable);
t.start();
System.out.println(t.getState());
这段代码很可能输出:
RUNNABLE
注意:在此示例中,当执行到
t.getState()时,线程可能已被调度器立即执行完毕。因此,有时也可能看到其他状态(如TERMINATED),这取决于线程调度的具体时机。
3.3. BLOCKED(阻塞)
当线程试图进入一个被其他线程锁定的同步代码块或方法时,它将进入 BLOCKED 状态,直到获得所需的监视器锁。
下面的代码演示了如何使线程进入 BLOCKED 状态:
public class BlockedState {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new DemoBlockedRunnable());
Thread t2 = new Thread(new DemoBlockedRunnable());
t1.start();
t2.start();
Thread.sleep(1000);
System.out.println(t2.getState());
System.exit(0);
}
}
class DemoBlockedRunnable implements Runnable {
@Override
public void run() {
commonResource();
}
public static synchronized void commonResource() {
while(true) {
// 无限循环模拟长时间处理
// 't1' 不会退出此方法
// 当 't2' 尝试进入时会被阻塞
}
}
}
代码说明:
- 创建了两个线程
t1和t2; t1启动后进入synchronized修饰的commonResource()方法,该方法一次只允许一个线程访问;t1在方法内陷入无限循环,模拟长时间占用;- 当
t2启动并尝试进入同一方法时,由于锁已被t1持有,t2将进入 BLOCKED 状态; - 此时调用
t2.getState(),输出为:
BLOCKED
3.4. WAITING(等待)
当一个线程无限期地等待另一个线程执行特定操作时,它就处于 WAITING 状态。根据 Java 官方文档,以下三种方法会使线程进入此状态:
object.wait()thread.join()LockSupport.park()
注意:
wait()和join()在此处不带超时参数;带超时的情况属于下一节的 TIMED_WAITING。
现在,我们通过以下代码复现 WAITING 状态:
public class WaitingState implements Runnable {
public static Thread t1;
public static void main(String[] args) {
t1 = new Thread(new WaitingState());
t1.start();
}
public void run() {
Thread t2 = new Thread(new DemoWaitingStateRunnable());
t2.start();
try {
t2.join(); // t1 在此处等待 t2 结束
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
}
class DemoWaitingStateRunnable implements Runnable {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
System.out.println(WaitingState.t1.getState());
}
}
逻辑说明:
- 创建并启动
t1; t1内部创建并启动t2;t1调用t2.join(),从而进入 WAITING 状态,直到t2执行完毕;- 在
t2中打印t1的状态;
输出结果为:
WAITING
3.5. TIMED_WAITING(计时等待)
当线程在指定时间内等待另一个线程执行特定操作时,它处于 TIMED_WAITING 状态。
根据 Java 文档,以下五种方式可使线程进入此状态:
Thread.sleep(long millis)Object.wait(long timeout)或wait(long timeout, int nanos)Thread.join(long millis)LockSupport.parkNanos()LockSupport.parkUntil()
下面是一个快速复现 TIMED_WAITING 状态的示例:
public class TimedWaitingState {
public static void main(String[] args) throws InterruptedException {
DemoTimeWaitingRunnable runnable = new DemoTimeWaitingRunnable();
Thread t1 = new Thread(runnable);
t1.start();
// 给线程调度器足够时间启动 t1
Thread.sleep(1000);
System.out.println(t1.getState());
}
}
class DemoTimeWaitingRunnable implements Runnable {
@Override
public void run() {
try {
Thread.sleep(5000); // 睡眠 5 秒
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
}
这里,t1 启动后立即进入 5 秒的睡眠状态,因此在主线程中打印其状态时,输出为:
TIMED_WAITING
3.6. TERMINATED(终止)
TERMINATED 是线程的最终状态,表示线程已正常执行完毕或因异常而终止。
我们另有一篇文章专门讨论如何正确停止线程。
下面的代码演示如何使线程进入 TERMINATED 状态:
public class TerminatedState implements Runnable {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new TerminatedState());
t1.start();
// 给 t1 足够时间完成执行
Thread.sleep(1000);
System.out.println(t1.getState());
}
@Override
public void run() {
// 无任何处理逻辑,立即结束
}
}
由于 t1 的 run() 方法为空,启动后几乎立即结束。主线程休眠 1 秒后检查其状态,输出为:
TERMINATED
此外,我们还可以使用 isAlive() 方法判断线程是否仍在运行。例如:
Assert.assertFalse(t1.isAlive());
该方法返回 false,说明线程已死亡。简单来说,只有当线程已启动且尚未终止时,isAlive() 才返回 true。
4. 结论
在本教程中,我们深入学习了 Java 线程的完整生命周期,详细介绍了 Thread.State 枚举定义的六种状态,并通过简洁的代码示例复现了每一种状态。
尽管这些代码在大多数机器上会产生一致的输出,但在某些特殊情况下(如线程调度时机不同),可能会观察到不同的结果,因为线程调度器的确切行为是不可预测的。