【Java EE】深入理解 Java 线程的生命周期与状态转换

devtools/2024/10/22 17:24:14/

多线程编程在 Java 中是实现高效并发的核心技术之一。每个线程在其生命周期内会经历多个状态,这些状态反映了线程在特定时间点的行为与系统资源的使用情况。了解线程的状态及其转换机制,对于编写健壮的并发程序尤为重要。本文将深入探讨 Java 线程的六种状态、每种状态的含义、状态之间的转换条件以及线程状态在实际应用中的意义。

1. Java 线程的六种状态

根据 Java 虚拟机规范,线程状态可以被分为六种:

  1. 新建(New)
  2. 可运行(Runnable)
  3. 阻塞(Blocked)
  4. 等待(Waiting)
  5. 计时等待(Timed Waiting)
  6. 终止(Terminated)

这些状态为理解线程的生命周期提供了基本框架,帮助开发者掌握线程在不同场景下的行为。

2. 线程状态的含义详解

(1)新建(New)

线程处于新建状态时,它仅仅是一个被创建但尚未启动的对象。在这个状态下,线程对象已经被实例化,但线程还未被调度器纳入可运行线程的集合中。此时,线程还没有占用任何系统资源。

java">Thread thread = new Thread(() -> {System.out.println("线程处于新建状态");
});
// 线程尚未启动,处于新建状态

(2)可运行(Runnable)

可运行状态的线程表示它已经准备好执行代码,但并不一定立即执行。Java 中的可运行状态不仅包括真正占用 CPU 时间片的状态,也包括线程在操作系统层面处于等待资源调度的状态。线程在调用 start() 方法后,从新建状态转为可运行状态。

java">thread.start(); // 线程进入可运行状态

可运行状态的线程可以随时被操作系统调度执行,而调度的时机取决于系统的线程调度策略(例如时间片轮转、优先级调度等)。

(3)阻塞(Blocked)

阻塞状态是线程生命周期中的一个重要环节,通常发生在线程等待获取一个被其他线程持有的锁时。当线程试图进入同步代码块或者同步方法时,如果无法获得锁,它将进入阻塞状态。阻塞状态的线程无法继续执行,直到它成功获取锁为止。

java">public class Main {private static final Object lock = new Object();public static void main(String[] args) {Thread thread1 = new Thread(() -> {synchronized (lock) {System.out.println(Thread.currentThread().getName() + " 获得了锁");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});Thread thread2 = new Thread(() -> {synchronized (lock) {System.out.println(Thread.currentThread().getName() + " 获得了锁");}});thread1.start();thread2.start();}
}

在这个例子中,thread1 先获得锁并进入同步代码块,thread2 尝试获取相同的锁,但在 thread1 释放锁之前,thread2 会进入阻塞状态。

(4)等待(Waiting)

等待状态表示线程在等待某个条件的发生,只有当另一个线程显式地唤醒它时,线程才能从等待状态返回到可运行状态。典型的场景包括调用 Object.wait()Thread.join()LockSupport.park() 等方法。

java">public class Main {private static final Object lock = new Object();public static void main(String[] args) {Thread thread = new Thread(() -> {synchronized (lock) {try {System.out.println("线程进入等待状态");lock.wait(); // 线程进入等待状态System.out.println("线程恢复执行");} catch (InterruptedException e) {e.printStackTrace();}}});thread.start();try {Thread.sleep(1000);synchronized (lock) {lock.notify(); // 唤醒等待线程}} catch (InterruptedException e) {e.printStackTrace();}}
}

在此示例中,线程在 wait() 方法调用后进入等待状态,直到其他线程调用 notify() 方法将其唤醒。

(5)计时等待(Timed Waiting)

计时等待状态与等待状态类似,区别在于计时等待是有时间限制的。如果线程在指定的时间内没有被唤醒,它会自动从计时等待状态返回到可运行状态。计时等待通常用于控制线程的超时行为,比如通过 Thread.sleep()Object.wait(long timeout)Thread.join(long millis) 等方法来实现。

java">public class Main {public static void main(String[] args) {Thread thread = new Thread(() -> {try {System.out.println("线程进入计时等待状态");Thread.sleep(3000); // 线程进入计时等待状态System.out.println("线程恢复执行");} catch (InterruptedException e) {e.printStackTrace();}});thread.start();}
}

在这个示例中,线程通过 Thread.sleep(3000) 进入计时等待状态,3秒后线程会自动恢复执行。

(6)终止(Terminated)

当线程的 run() 方法执行完成或由于未捕获的异常导致线程退出时,线程将进入终止状态。处于终止状态的线程不再具备可运行性,也不能被重新启动。在这种状态下,线程已经完成了其生命周期的所有阶段。

java">public class Main {public static void main(String[] args) {Thread thread = new Thread(() -> {System.out.println("线程运行完成");});thread.start();try {thread.join(); // 等待线程终止System.out.println("线程状态: " + thread.getState()); // 输出:TERMINATED} catch (InterruptedException e) {e.printStackTrace();}}
}

在该示例中,thread.join() 方法等待线程终止,终止后的线程状态为 TERMINATED

3. 线程状态之间的转换条件

线程的状态在其生命周期内根据特定条件进行转换。了解这些转换条件有助于我们更好地掌控线程的行为,并在开发过程中避免常见的并发问题。

  • 新建 -> 可运行:调用 start() 方法时,线程从新建状态进入可运行状态。
  • 可运行 -> 阻塞:线程试图获取一个被其他线程持有的锁时,会从可运行状态进入阻塞状态。
  • 阻塞 -> 可运行:当线程成功获取锁时,它会从阻塞状态返回到可运行状态。
  • 可运行 -> 等待:线程通过调用 Object.wait()Thread.join()LockSupport.park() 等方法进入等待状态。
  • 等待 -> 可运行:线程在被另一个线程显式唤醒(通过 Object.notify()notifyAll() 方法)后,从等待状态返回到可运行状态。
  • 可运行 -> 计时等待:线程通过调用具有超时参数的方法(如 Thread.sleep(long millis)Object.wait(long timeout) 等)进入计时等待状态。
  • 计时等待 -> 可运行:计时等待超时后,线程会自动从计时等待状态返回到可运行状态。
  • 可运行 -> 终止:当 run() 方法执行完成或线程因异常退出时,线程进入终止状态。

以下是线程状态转换的示意图:

        +--------------------+|                    |v                    |新建 (New) --> 可运行 (Runnable) <---> 计时等待 (Timed Waiting)|     ||     |阻塞 (Blocked)  |     |v     |等待 (Waiting)||终止 (Terminated)
4. 线程状态的实际应用与考虑

在实际开发中,线程状态的管理直接影响程序的正确性和性能。例如:

  • 死锁与阻塞状态:当多个线程相互等待对方释放资源时,可能会导致死锁问题,线程永远处于阻塞状态。合理设计锁的获取与释放机制可以避免死锁。

  • 等待/通知机制:在生产者-消费者模型中,生产者线程和消费者线程通过等待/通知机制实现高效的同步与通信。确保通知机制的正确性对于避免线程“假唤醒”非常关键。

  • 超时机制:通过计时等待,开发者可以为线程操作设置合理的超时时间,防止线程在等待过程中无限期地被挂起。超时机制在网络通信、文件 I/O 等场景中尤为重要。

结论

Java 线程的六种状态及其相互转换为我们提供了理解线程生命周期的基础。这些状态反映了线程在不同运行阶段的行为,也为我们管理线程提供了参考。在实际开发中,通过合理管理线程的状态,避免常见的并发问题,可以显著提升程序的可靠性和性能。

掌握线程的状态与转换规律,将使我们能够编写出更加健壮的并发程序,为复杂的多线程环境下提供稳定可靠的解决方案。出高效且安全的多线程程序。


http://www.ppmy.cn/devtools/104644.html

相关文章

【数据结构-前缀异或和】力扣1371. 每个元音包含偶数次的最长子字符串

给你一个字符串 s &#xff0c;请你返回满足以下条件的最长子字符串的长度&#xff1a;每个元音字母&#xff0c;即 ‘a’&#xff0c;‘e’&#xff0c;‘i’&#xff0c;‘o’&#xff0c;‘u’ &#xff0c;在子字符串中都恰好出现了偶数次。 示例 1&#xff1a; 输入&…

机器视觉-3 光学成像之明场与暗场

一. 原理介绍 在机器视觉中&#xff0c;光学成像的明场&#xff08;Bright Field&#xff09;和暗场&#xff08;Dark Field&#xff09;是两种常见的成像技术&#xff0c;分别用于不同的检测和分析场景。它们通过不同的光照方式来突出对象的特征&#xff0c;从而帮助识…

RK3568平台(UART篇)uart_driver 注册流程

一.串口子系统框架 串口子系统框架是 Linux 内核中专门用于处理串口设备的模块化框架: 在上图中,包含了多个层级,每个层级负责处理不同的功能和任务,从而实现串口设备的 完整驱动和管理。接下来依次介绍每个层级的作用。 应用层:位于最顶层,是串口子系统中用户空间应用程…

SQL 注入之 Oracle 注入

在 SQL 注入攻击的领域中&#xff0c;Oracle 数据库的注入攻击具有一定的特殊性和复杂性。Oracle 作为一种广泛使用的关系型数据库管理系统&#xff0c;其安全性一直备受关注。然而&#xff0c;由于应用程序开发中的漏洞或者不当配置&#xff0c;Oracle 数据库仍然可能成为 SQL…

Leetcode3244. 新增道路查询后的最短距离 II

Every day a Leetcode 题目来源&#xff1a;3244. 新增道路查询后的最短距离 II 解法1&#xff1a;贪心 由于题目保证添加的边&#xff08;捷径&#xff09;不会交叉&#xff0c;从贪心的角度看&#xff0c;遇到捷径就走捷径是最优的。所有被跳过的城市都不可能再出现在最短…

css中的伪类

什么是伪类 伪类&#xff08;Pseudo-classes&#xff09;是 CSS 中的一个重要概念&#xff0c;它们用于定义元素的特定状态。伪类可以基于元素的特定属性或状态来选择和样式化文档树中的元素&#xff0c;而不需要使用类或 ID。伪类通常以冒号 : 开头。 用法 :link - 选择未被…

【大模型LLM第十一篇】微调自动化数据选择方式之MoDS

前言 来自中科院自动化所的paper MoDS: Model-oriented Data Selection for Instruction Tuning link&#xff1a;https://arxiv.org/pdf/2311.15653 github&#xff1a;https://github.com/CASIA-LM/MoDS 一、摘要 sft已经成为让LLM遵循用户指令的一种方式。通常&#xf…

前端缓存机制及其特点

1、localStorage localStorage 是一种 Web 存储&#xff08;Web Storage&#xff09;技术&#xff0c;它属于浏览器提供的客户端存储机制。localStorage 的特点使它被广泛用于持久性的数据存储&#xff0c;即使在浏览器关闭并重新打开之后&#xff0c;数据仍然保留。 localSt…