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

news2024/9/25 19:23:13

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() {
    @Override
    public 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 一点时间进入 sleep
        System.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.coloradmin.cn/o/1677457.html

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

相关文章

PHP黑魔法之strcmp与is_numeric绕过

1、strcmp绕过 PHP手册: int strcmp ( string $str1 , string $str2 )Return ValuesReturns < 0 if str1 is less than str2; > 0 if str1 is greater than str2, and 0 if they are equal 当输入的两个值为不是字符串时就会产生不预期的返回值 strcmp()在比较字符串和…

PyQt5编写的一个简易图像处理软件

文章目录 1. 简介2. 准备工作3. 主界面设计4. 功能构建5. 总结 1. 简介 通过编写简易图像处理软件&#xff0c;你可以学习如何使用 PyQt5 构建用户界面&#xff0c;以及如何与用户交互。同时&#xff0c;你还可以学习图像处理技术&#xff0c;如图像读取、傅里叶变换、滤波、增…

三、安装node_exporter

目录 一、简介 二、下载安装 一、简介 Exporter是Prometheus的指标数据收集组件。它负责从目标Jobs收集数据&#xff0c;并把收集到的数据转换为Prometheus支持的时序数据格式。 和传统的指标数据收集组件不同的是&#xff0c;他只负责收集&#xff0c;并不向Server端发送数据…

百度文心一言 java 支持流式输出,Springboot+ sse的demo

参考&#xff1a;GitHub - mmciel/wenxin-api-java: 百度文心一言Java库&#xff0c;支持问答和对话&#xff0c;支持流式输出和同步输出。提供SpringBoot调用样例。提供拓展能力。 1、依赖 <dependency> <groupId>com.baidu.aip</groupId> <artifactId…

Vue3使用datav3报错的三个问题解决

我这里写的是按需引入 报错问题Cannot find module dataview/datav-vue3 修改datav源码中的package.json文件 修改为 "module": "./es/index.mjs", 然就就会遇见新的报错问题 报错问题TypeError: Cannot read properties of null (reading $el) 然后修改…

Day 46 139.单词拆分

单词拆分 给定一个非空字符串 s 和一个包含非空单词的列表 wordDict&#xff0c;判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。 说明&#xff1a; 拆分时可以重复使用字典中的单词。 你可以假设字典中没有重复的单词。 示例 1&#xff1a; 输入: s “leet…

智能监控与安全管理:安全帽检测算法的实践与应用

在工地、煤矿等高危工作环境中&#xff0c;安全帽的佩戴至关重要。安全帽能够有效防止因坠落物体或碰撞等引起的头部伤害&#xff0c;从而保护工作人员的生命安全。然而&#xff0c;传统的检查人员佩戴安全帽的方式主要依赖于现场监督和巡查&#xff0c;这种方法不仅耗费大量人…

PDF编辑阅读器PDF Expert for Mac v3.10.1中文激活版

PDF Expert for Mac是一款易于使用的 PDF 编辑器和注释器&#xff0c;专为 Mac 设备设计。它允许用户轻松查看、编辑、签名、注释和共享 PDF。该软件使用户能够向他们的 PDF 添加文本、图像、链接和形状&#xff0c;突出显示和标记文本&#xff0c;填写表格以及签署数字文档。它…

更高、更快、更强,受管文件传输应该注意的三个要素

受管文件在很多公司内部都存在这个分类&#xff0c;受管文件不是特定的某一文件&#xff0c;而是指的是在一个组织或企业内&#xff0c;被正式管理和控制的文件。这些文件通常包含重要的信息&#xff0c;如技术规格、标准操作程序&#xff08;SOPs&#xff09;、质量手册、设计…

SDN 实现 vxlan隧道

SDN vxlan隧道 官方介绍&#xff1a; VXLAN&#xff08;Virtual eXtensible Local Area Network&#xff0c;虚拟扩展局域网&#xff09;&#xff0c;是由IETF定义的NVO3&#xff08;Network Virtualization over Layer 3&#xff09;标准技术之一&#xff0c;是对传统VLAN协议…

社交媒体数据恢复:如流

如流&#xff0c;原名百度Hi&#xff0c;是百度公司开发的一款即时通讯软体。百度Hi具备文字消息、视讯、通话、文件传输等功能。 查找备份&#xff1a;如果您之前有备份如流中的数据&#xff0c;您可以尝试从备份中恢复。如流支持备份至云端&#xff0c;如百度网盘等。 联系客…

C++auto关键字、范围for循环

一、auto关键字 1.1auto简介 在早期C/C中auto的含义是&#xff1a;使用auto修饰的变量&#xff0c;是具有自动存储器的局部变量。 C11中&#xff0c;标准委员会赋予了auto全新的含义即&#xff1a;auto不再是一个存储类型指示符&#xff0c;而是作为一个新的类型指示符来指示编…

前端铺子后台管理系统:基于Vue3与Ant Design Vue的轻量级解决方案

一、引言 随着前端技术的飞速发展&#xff0c;构建高效、轻量且易于维护的后台管理系统成为了企业信息化建设的重要一环。前端铺子后台管理系统&#xff0c;作为一款基于Vue的前端框架&#xff0c;结合Ant Design Vue的UI组件库&#xff0c;为企业提供了一个高效、灵活的后台管…

视频推拉流/视频直播点播平台EasyDSS使用Mysql数据库接口报错502如何处理?

视频推拉流/视频直播点播EasyDSS互联网直播平台支持一站式的上传、转码、直播、回放、嵌入、分享功能&#xff0c;具有多屏播放、自由组合、接口丰富等特点。平台可以为用户提供专业、稳定的直播推流、转码、分发和播放服务&#xff0c;全面满足超低延迟、超高画质、超大并发访…

计算思维的理解

2006年&#xff0c;卡内基梅隆大学周以真教授首次系统性地定义了计算思维。这一年&#xff0c;她在美国计算机权威期刊《Communications of the ACM》上发表了题为《Computational Thinking》的论文&#xff0c;由此开启了计算思维大众化的全新历程。 周以真&#xff08;Jeanne…

指针(4)

目录 1. 字符指针变量 2.数组指针 2.1 数组指针和指针数组的区别 2.2访问数组指针 3. ⼆维数组传参的本质 4. 函数指针变量 4.1两段有趣的代码 4.2 typedef 关键字 5.函数指针数组 6.转移表 1. 字符指针变量 在指针的类型中我们知道有⼀种指针类型为字符指针 char* …

【devops】Linux 日常磁盘清理 ubuntu 清理大文件 docker 镜像清理

日常磁盘清理 1、查找大文件 find / -type f -size 1G2、清理docker无用镜像&#xff08;drone产生的残余镜像文件&#xff09; docker system prune -a一、清理服务器磁盘 1、查找大文件 在Ubuntu系统中&#xff0c;你可以使用find命令来查找大文件。find命令是一个强大的…

渣土车上路识别报警摄像机

随着城市建设的不断推进&#xff0c;渣土车在城市道路上的数量也逐渐增加。然而&#xff0c;一些不法渣土车司机往往会超载、超速行驶或者闯红灯&#xff0c;给道路交通安全和城市环境带来了一定的隐患。为了有效监管渣土车上路行驶的情况&#xff0c;渣土车上路识别报警摄像机…

企业微信hook接口协议,ipad协议http,获取二次验证二维码接口

获取二次验证二维码接口 参数名必选类型说明uuid是String每个实例的唯一标识&#xff0c;根据uuid操作具体企业微信 请求示例 {"uuid":"f5a22e9b-9664-4250-b40a-08741dba549c" } 返回示例 {"data": {"qrcode": "http://47.9…

Linux之内存管理-malloc \kmalloc\vmalloc\dma

1、malloc 函数 1.1分配内存小于128k,调用brk malloc是C库实现的函数&#xff0c;C库维护了一个缓存&#xff0c;当内存够用时&#xff0c;malloc直接从C库缓存分配&#xff0c;只有当C库缓存不够用&#xff1b; 当申请的内存小于128K时&#xff0c;通过系统调用brk&#xff…