Java线程生命周期:Java线程生命周期全景解读

1. 线程生命周期概述:不仅仅是状态转换

在多线程编程中,理解线程的生命周期对于编写有效、高效的代码至关重要。线程生命周期通常描述了线程从创建到死亡的一系列状态变化过程,但其实不仅仅局限于这些状态的简单转换。线程生命周期的理解应该考虑系统资源的分配、线程调度、同步、通信,以及在这些状态转换中所涉及的复杂机制。
在现代操作系统中,线程生命周期涉及的五个基本状态分别是:

  • 新建 (New)
  • 就绪 (Runnable)
  • 运行 (Running)
  • 阻塞 (Blocked)
  • 死亡 (Terminated)

然而,操作系统和JVM的线程调度策略、同步机制(如锁)、等待/通知模式以及线程自身的方法调用,都会对这些状态产生影响,使得线程的生命周期变得更加复杂。本文将探讨Java中线程生命周期的每个状态,并通过丰富的实例代码展示它们之间的转换是如何发生的,以及程序员需要在实际编程工作中注意哪些问题。

以下是一个基本线程生命周期的Java代码示例:

public class ThreadLifeCycleExample implements Runnable {public void run() {// 线程处于运行状态时的逻辑处理try {// 模拟线程执行任务Thread.sleep(1000);// 线程可能处于TIMED_WAITING状态,因为调用了sleep方法} catch (InterruptedException e) {// 对中断异常的处理Thread.currentThread().interrupt();}}public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(new ThreadLifeCycleExample());// 在这点,线程处于 NEW 状态System.out.println("Thread state after creation: " + thread.getState());thread.start();// 启动线程后,线程处于 RUNNABLE 状态(在Java中RUNNABLE包含了就绪和运行状态)System.out.println("Thread state after calling .start(): " + thread.getState());// 主线程暂停,确保上面的线程运行Thread.sleep(100);System.out.println("Thread state after calling .sleep() in main: " + thread.getState());// 等待线程终止thread.join();System.out.println("Thread state after completion: " + thread.getState());}
}

2. 深入Java线程状态

file
线程在Java中是通过Thread类来实现的,它封装了线程的状态以及行为。根据Java线程生命周期,线程可以存在于以下几种状态之一:

  1. 新建 (NEW)
  2. 可运行 (RUNNABLE)
  3. 阻塞 (BLOCKED)
  4. 等待 (WAITING)
  5. 计时等待 (TIMED_WAITING)
  6. 终止 (TERMINATED)

这些状态在java.lang.Thread.State枚举中有定义。接下来,我们将深入分析每个状态。

2.1. NEW:线程的诞生

一个线程在被创建出来,但是还没有调用start()方法之前,处于NEW状态。此时,它被实例化但尚未开始执行。

2.2. RUNNABLE:准备运行的双面性

在Java编程中,RUNNABLE状态实际上包含了传统意义上的"可运行"和"运行"两种状态。即线程已经被启动且可以被线程调度器执行(可运行),或已经在执行了(运行)。然而,即使线程处于RUNNABLE状态,是否正在执行由操作系统的线程调度器决定。

2.3. BLOCKED与WAITING:阻塞与等待的微妙差别

BLOCKED状态通常发生在线程试图获取一个锁时,但该锁正被其他线程持有。WAITING状态发生于等待其他线程特定的通知(notification)时,例如调用了Object.wait()方法,而没有任何时间限制。

2.4. TIMED_WAITING:带有时限的挂起

线程可以通过调用一些带有指定等待时间的方法(如Thread.sleep(long millis)或Object.wait(long timeout))进入TIMED_WAITING状态,这意味着线程在一个特定的时间之后会自动返回RUNNABLE状态。

2.5. TERMINATED:终结并非终点

一旦线程的run()方法执行完毕,或者线程被中断,它就会进入TERMINATED状态。线程一旦终结,它的生命周期就结束了,不能重新启动。

3. 详细分析线程状态转换

理解线程状态之间的转换对于管理线程生命周期至关重要。这些状态转换可能会受代码逻辑、线程管理API的使用以及操作系统调度策略的影响。在这一章节,我们将详细探讨各个转换点,并通过实际案例来演示它们是如何在Java中实现的。

3.1. 如何从NEW到RUNNABLE:线程的启动

当一个线程被创建时,它首先处于NEW状态。一旦调用了线程对象的start()方法,该线程被移动到RUNNABLE状态,并等待线程调度器的调度执行。以下是一个示例代码:

Thread thread = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("Thread is running.");}
});
System.out.println("State after thread creation: " + thread.getState());  // 应打印 NEW
thread.start();
System.out.println("State after calling start(): " + thread.getState());  // 应打印 RUNNABLE

3.2. RUNNABLE与BLOCKED的拮抗:同步造成的结果

一个线程的状态从RUNNABLE变为BLOCKED通常是因为它试图获得一个已被其他线程锁定的对象锁。这里展示了如何使用synchronized关键字来演示这个转换:

public class BlockedStateDemo {private static final Object lock = new Object();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(BlockedStateDemo::run);Thread t2 = new Thread(BlockedStateDemo::run);t1.start();t2.start();Thread.sleep(10);  // 确保线程 t1 运行起来System.out.println("State of t2 after attempting to enter synchronized block: " + t2.getState());// 应打印 BLOCKED}private static void run() {synchronized (lock) {try {Thread.sleep(100);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}}
}

3.3. RUNNABLE到WAITING的过渡:对象监视器模型

当线程需要等待其他线程执行某个特定操作时,它会进入WAITING状态。一个典型的例子是调用Object类的wait()方法:

public class WaitingStateDemo {private static final Object lock = new Object();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(WaitingStateDemo::run);t1.start();Thread.sleep(10);  // 给 t1 时间达到 wait() 调用System.out.println("State of t1: " + t1.getState()); // 应该打印 WAITING}private static void run() {synchronized (lock) {try {lock.wait();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}}
}

3.4. WAITING到TIMED WAITING的条件:时间控制的等待

与WAITING状态相似,TIMED_WAITING状态涉及一个时间的概念。当线程调用一个带有指定等待时间的方法时,它会进入此状态,例如Thread.sleep(long millis):

public class TimedWaitingStateDemo {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {try {Thread.sleep(5000);} catch (InterruptedException e) {Thread.currentThread().interrupt();}});t1.start();Thread.sleep(10);  // 给 t1 一点时间进入 sleepSystem.out.println("State of t1 after calling sleep(): " + t1.getState()); // 应打印 TIMED_WAITING}
}

3.5. 从RUNNABLE到TERMINATED状态:线程的结束

一旦线程的run()方法执行完毕或者它被中断,它就会进入TERMINATED状态:

public class TerminatedStateDemo {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {// 无任务,直接退出});t1.start();t1.join(); // 等待 t1 结束System.out.println("State of t1 after it has finished execution: " + t1.getState()); // 打印 TERMINATED}
}

4. 线程状态转换的实战案例分析

在这一章节,我们将通过几个编程案例,详细展示线程状态的转换,并解释在这些状态转换中可能遇到的问题。这将帮助开发人员更好地掌握线程状态管理,并避免常见的多线程编程陷阱。

4.1. 状态转换演示:编写示例代码解释状态转换

我们将首先展示一个简单的案例,这个案例会创建多个线程,并模拟阻塞、等待和超时等待的场景,来观察和理解线程状态的转换。

示例代码:

public class ThreadStateTransitionDemo {private static final Object LOCK = new Object();public static void main(String[] args) throws Exception {// 新建一个线程,它会进行一段时间的计时等待Thread timedWaitingThread = new Thread(() -> {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}});// 新建一个线程,它会无限期等待直到获得锁Thread waitingThread = new Thread(() -> {synchronized (LOCK) {try {LOCK.wait();} catch (InterruptedException e) {e.printStackTrace();}}});// 新建一个线程,尝试获取锁,但是会被 waitingThread 阻塞Thread blockedThread = new Thread(() -> {synchronized (LOCK) {System.out.println("Blocked Thread 获取到了锁");}});System.out.println("启动各个状态的线程...");timedWaitingThread.start();waitingThread.start();Thread.sleep(100); // 确保 waitingThread 运行并进入 waiting 状态blockedThread.start();Thread.sleep(100); // 短暂等待,让线程尝试获取锁System.out.println("TimedWaitingThread 状态: " + timedWaitingThread.getState());System.out.println("WaitingThread 状态: " + waitingThread.getState());System.out.println("BlockedThread 状态: " + blockedThread.getState());// 发起通知,让 waitingThread 退出等待状态synchronized (LOCK) {LOCK.notify();}// 等待所有线程完成timedWaitingThread.join();waitingThread.join();blockedThread.join();System.out.println("所有线程都完成了执行。");}
}

这段代码首先创建了三个线程,它们分别模拟了计时等待(timed waiting)、无限期等待(waiting)以及阻塞(blocked)三种状态。通过调用 getState() 方法,我们可以查看每个线程的状态,然后触发状态转换进行验证。

4.2. 实例解析:挖掘阻塞、等待和超时等待的场景

接下来,我们通过上面的代码来解说不同状态下的线程是如何响应的。这有助于开发者理解在并发编程中如何正确处理同步和线程协调的关键部分。

4.3. 死锁与线程生命周期的关联

最后,我们将展示一个引入死锁的示例来说明线程状态管理如果不当,可能会导致应用程序无响应的情况。死锁的情形通常涉及两个或两个以上的线程,它们彼此等待对方释放锁,但却永远不会发生,因此线程将保持阻塞状态。

示意代码:

public class DeadlockDemo {private static final Object RESOURCE1 = new Object();private static final Object RESOURCE2 = new Object();public static void main(String[] args) {new Thread(() -> {synchronized (RESOURCE1) {System.out.println("Thread 1: Locked resource 1");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (RESOURCE2) {System.out.println("Thread 1: Locked resource 2");}}}).start();new Thread(() -> {synchronized (RESOURCE2) {System.out.println("Thread 2: Locked resource 2");synchronized (RESOURCE1) {System.out.println("Thread 2: Locked resource 1");}}}).start();}
}

代码中,两个线程互相持有对方需要的资源,且都在等待对方释放资源。这种情况就是典型的死锁。

5. 性能监控与线程状态:工具与实践

了解和监控Java线程的状态对于确保应用程序性能至关重要。本章节将介绍几种常用的工具,这些工具可以帮助开发者监控和分析线程的行为和状态,从而进行性能优化和故障排查。

5.1. 使用JConsole监控线程状态

JConsole是Java开发者工具箱中的标准工具,它可以通过JMX(Java Management Extensions)连接到运行中的Java应用程序,提供应用程序的实时监控数据,其中包括线程的状态信息。
当您想要查看和监控线程状态时,可启动JConsole并连接到您的Java应用程序。它将提供一个线程标签页,显示所有活动线程的列表和它们当前的状态,包括锁持有情况,以及死锁探测器等有用信息。
使用JConsole监控线程状态的步骤大致如下:

  1. 启动Java应用程序,确保在启动参数中开启JMX端口。
  2. 启动JConsole并连接到相应的JMX端口。
  3. 在’线程’标签页中观察线程状态和其他关键信息。

5.2. 借助VisualVM进行线程性能剖析

VisualVM是另一款强大的监控工具,它不仅能监控线程状态,还能提供CPU和内存的实时数据,线程转储,以及性能分析(Profiler)等功能。
通过VisualVM,开发者可以很容易地捕获线程转储(thread dump),分析应用程序中的线程是如何互相作用的,识别出性能瓶颈或不必要的同步操作。
VisualVM使用的基本步骤如下:

  1. 启动VisualVM。
  2. 选择要监控的Java应用程序。
  3. 查看’线程’标签,进行线程的实时监控或者捕获线程转储。

5.3. 编程避坑:线程状态不当转换导致的性能问题

在多线程编程中,不恰当的线程状态转换可能会导致严重的性能问题,如过度的线程创建和销毁、频繁的上下文切换、死锁和资源饥饿等。开发人员需学会使用以上工具诊断这些问题,并采取适当策略来优化代码,比如正确的同步策略,合理的线程池使用,以及有效的线程通信机制等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/837883.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

应急响应-Windows-挖矿病毒

随着虚拟货币市场的繁荣,挖矿病毒已成为网络安全领域一大挑战。该类病毒利用计算机资源进行加密货币的挖掘,给个人用户和企业网络带来了严重的安全风险。本文将针对挖矿病毒的应急响应和防范措施进行分析和总结。 一.判断挖矿病毒 服务器突然发现CPU资…

02-结构型设计模式(共7种)

1. Adapter(适配器模式) 适配器模式是一种结构型设计模式,它允许将一个类的接口转换成客户端所期望的另一个接口。这种模式通常用于解决接口不兼容的情况,使得原本由于接口不匹配而无法工作的类可以一起工作。 在 C 中,适配器模式可以通过类适…

Elasticsearch分词及其自定义

文章目录 分词发生的阶段写入数据阶段执行检索阶段 分词器的组成字符过滤文本切分为分词分词后再过滤 分词器的分类默认分词器其他典型分词器 特定业务场景的自定义分词案例实战问题拆解实现方案 分词发生的阶段 写入数据阶段 分词发生在数据写入阶段,也就是数据索…

AVL树、红黑树

数据结构、算法总述:数据结构/算法 C/C-CSDN博客 AVL树 定义 空二叉树是一个 AVL 树如果 T 是一棵 AVL 树,那么其左右子树也是 AVL 树,并且 ,h 是其左右子树的高度树高为 平衡因子:右子树高度 - 左子树高度 创建节点…

HBase无法给用户赋权的解决方案

建表之后,在赋权的时候,发现有错误 2.以开始以为语法有错误,不会啊,很简单的语法。经过测试几个命令发现,但凡和权限相关的命令,都失败了 百度到一些建议,需要检查参数,在确认下面…

用vue实现json模版编辑器

用vue实现json模版编辑器 控件区表单区配置项区 (还没写)业务逻辑 设想业务逻辑是拖拽控件生成表单 动手做了一个简单的demo 业务的原型图设想如下所示 其中使用的技术主要是vuedragger 控件区 做控件区的时候首先我们要有确定的配置项 其实也很简单 …

Github20K星开源团队协作工具:Zulip

Zulip:让团队协作的每一次交流,都精准高效。- 精选真开源,释放新价值。 概览 随着远程工作的兴起和团队协作的需求不断增加,群组聊天软件成为了日常工作中不可或缺的一部分。Zulip 是github上一个开源的团队协作工具,…

SpringBoot:缓存

点击查看SpringBoot缓存demo:LearnSpringBoot09Cache-Redis 技术摘要 注解版的 mybatisCacheConfigCacheableCachePut:既调用方法,又更新缓存数据;同步更新缓存CacheEvict:缓存清除Caching:定义复杂的缓存…

【运维自动化-配置平台】如何自动应用主机属性

主要用于配置主机属性的自动应用。当主机发生模块转移或模块新加入主机时,会根据目标模块配置的策略自动触发修改主机属性,比如主机负责人、主机状态。主机属性自动应用顾名思义是应用到主机上,而主机是必须在模块下的,所以有两种…

net 7部署到Linux并开启https

1.修改代码设置后台服务运行 安装nuget包 Install-Package Microsoft.Extensions.Hosting.WindowsServices Install-Package Microsoft.Extensions.Hosting.Systemd在Program代码中设置服务后台运行 var builder WebApplication.CreateBuilder(args);if (System.OperatingS…

【QuikGraph】C#调用第三方库计算有向图、无向图的连通分量

QuikGraph库 项目地址:https://github.com/KeRNeLith/QuikGraph 相关概念 图论、连通分量、强连通分量相关概念,可以从其他博客中复习: https://blog.csdn.net/weixin_50564032/article/details/123289611 https://zhuanlan.zhihu.com/p/3…

【LeetCode刷题】136.只出现一次的数字(Ⅰ)

【LeetCode刷题】136.只出现一次的数字(Ⅰ) 1. 题目:2.思路分析:思路1:一眼异或! 1. 题目: 2.思路分析: 思路1:一眼异或! 看到题目,如果有一定基…

Kafka学习-Java使用Kafka

文章目录 前言一、Kafka1、什么是消息队列offset 2、高性能topicpartition 3、高扩展broker 4、高可用replicas、leader、follower 5、持久化和过期策略6、消费者组7、Zookeeper8、架构图 二、安装Zookeeper三、安装Kafka四、Java中使用Kafka1、引入依赖2、生产者3、消费者4、运…

JavaScript异步编程——09-Promise类的方法【万字长文,感谢支持】

Promise 类的方法简介 Promise 的 API 分为两种: Promise 实例的方法(也称为:Promis的实例方法) Promise 类的方法(也称为:Promise的静态方法) 前面几篇文章,讲的都是 Promise 实…

USB3.0接口——(3)协议层(包格式)

1.协议层 1.1.超高速传输事务 超高速事务(SuperSpeed transactions)由主机对设备端点请求或发送数据开始,并在端点发送数据或确认收到数据时完成。 超高速总线上的数据传输(transfer)是主机请求设备应用程序生成的数…

【LAMMPS学习】九、LAMMPS脚本 示例

9. 示例脚本 LAMMPS 发行版包含一个包含许多示例问题的示例子目录。许多是二维模型,运行速度快且易于可视化,在台式机上运行最多需要几分钟。每个问题都有一个输入脚本 (in.*),并在运行时生成一个日志文件 (log.*)。有些使用初始坐标的数据文…

Linux-基础IO

🌎Linux基础IO 文章目录: Linux基础IO C语言中IO交互       常用C接口         fopen         fputs         fwrite         fgets 当前路径       三个文件流 系统文件IO       open函数     …

特征模态分解(FMD):一种小众而又新颖的分解方法

​ 声明:文章是从本人公众号中复制而来,因此,想最新最快了解各类智能优化算法及其改进的朋友,可关注我的公众号:强盛机器学习,不定期会有很多免费代码分享~ 今天为大家介绍一个小众而又新颖的信号分…

tensorflow实现二分类

# 导入所需库和模块 from tensorflow.keras.layers import Dense, Input, Activation # 导入神经网络层和激活函数模块 from tensorflow.keras.models import Sequential # 导入Keras的Sequential模型 import pandas as pd # 导入Pandas库用于数据处理 import numpy as np …

接口文档不显示新写的接口

新写的接口,但是不显示: 仔细对比源码才发现没有写tag: 然后就有了: