Java 线程的生命周期和状态(实践加深理解)

news2024/11/16 9:16:50

一、常规回答(八股文)

线程的生命周期一共分为有6个状态,在某个时刻中,一个线程只会处在6个状态中的其中一种。

第1:初始状态(NEW) 当前的线程已经被创建了出来,但是还没有通过调用start()方法来启用。

第2:运行状态(RUNNABLE) 当前的进程已经调用了start()方法,进入了运行的状态,或者正在等待CPU的调度。

第3:阻塞状态(BLOCKED) 当前的线程会暂停执行,等待某些事件的发生,比如等待锁的释放。

第4:等待状态(WAITING) 当前线程会暂停执行,等待其他线程的特定事件。
如果线程调用wait()方法导致暂停,则需其他线程调用notify方法唤醒当前线程;如果线程调用了其他线程的join()方法,则需等待join对应的线程执行完毕后,才能唤醒当前线程。

第5:计时等待状态(TIMED_WAITING) 线程暂停执行,但是在经历了一段时间后会自动被唤醒。如调用Thread.sleep()方法,一段时间内线程被阻塞。

第6:终止状态(TERMINATED) 当前线程的逻辑已经执行完毕,或者遇到了异常而终止,就会进入终止状态。

二、加深理解

加深理解面试题答案的最好方法,无非就是自己实践一次。

所以,为了能够检测到 Java 程序中各个线程的运行状态,这里使用了一个小工具:arthas-boot

2.1 arthas-boot 安装(可跳过)

官方文档:https://alibaba.github.io/arthas
下载连接:https://arthas.aliyun.com/arthas-boot.jar

arthas 实际上是 Alibaba 开源的 Java 诊断工具,它的特点是使用方便,功能强大

最近学习了 JVM 调优相关的内容,使用到了这个工具,所以这里才想到使用它来检测 Java 程序的线程状态信息(如下图),来更好的理解线程在什么时候会进入什么状态。

在这里插入图片描述
这里简单说一下使用步骤,如不想安装则可以跳过直接看:[点击跳转] 2.2 开始检验线程的状态

第1步, 下载 arthas-boot.jar 文件:https://arthas.aliyun.com/arthas-boot.jar

第2步, 在本地系统运行一个Java程序,比如在 IDEA 中写一个死循环并运行:

在这里插入图片描述

第3步, 打开cmd,进入文件所在目录,执行命令

java -jar arthas-boot.jar

在这里插入图片描述

随后选择需要挂载的 Java 程序,这里我们要监控的是 Test3 这个类,所以在控制台输入1并回车。

在这里插入图片描述

等到出现以下 arthas 的图案,就说明运行成功了。

在这里插入图片描述
输入 thread 命令,即可查看当前进程的所有运行的线程:

在这里插入图片描述

2.2 检测线程的状态

2.2.1 初始状态(NEW)

当一个线程被创建但还没有调用start()方法运行的时候,这时的线程状态应该是 NEW

这个状态下的线程用arthas工具获取不到,所以就只能用代码获取了。

测试程序:

public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        Thread test1 = new Thread(() -> {
            while (true) {
                // 模拟线程在执行某些操作
            }
        });
        test1.setName("test1");
        System.out.printf("线程[%s]当前的状态是: %s\n", test1.getName(), test1.getState());
    }
}

运行结果:

在这里插入图片描述

2.2.2 运行状态(RUNNABLE)

当一个线程被创建了,且调用了start()方法后,该线程状态应为RUNNABLE

测试程序:

public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        Thread test1 = new Thread(() -> {
            while (true) {
                // 模拟线程在执行某些操作
            }
        });
        test1.setName("test1");
        test1.start();
        doSomething();
    }
    
    /** 保持程序一直执行 */
    private static void doSomething() {
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:

在这里插入图片描述

2.2.3 阻塞状态(BLOCKED)

这个状态下的线程会暂停当前的执行。通常情况下,一个线程在等待锁(锁已被其他线程占用)的时候,这个线程会进入阻塞状态。

测试程序:

public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        Thread test1 = new Thread(() -> {
            enter();
        });
        Thread test2 = new Thread(() -> {
            enter();
        });
        /*
        	两个线程都尝试进入enter()方法,
        	但只有一个线程能够拿到 `synchronized` 锁并进入方法,
        	另外一个线程尝试进入方法但会被阻塞。
		*/
        test1.setName("test1");
        test2.setName("test2");
        test1.start();
        test2.start();
    }
    
    private synchronized static void enter() {
        System.out.printf("线程[%s]进来了,它将执行以下耗时的业务操作\n", Thread.currentThread().getName());
        for (long j = 0L; j < 50000000000L; j++) {
            
        }
        System.out.printf("线程[%s]退出了,释放了锁\n", Thread.currentThread().getName());
    }
}

运行结果:

# 控制台打印信息:
线程[test1]进来了,它将执行以下耗时的业务操作

(以上是目前为止已打印的结果)

此时,线程test1处于运行状态,而线程test2因为没有拿到锁(锁被test1占用),所以处在阻塞BLOCKED状态。

在这里插入图片描述

2.2.4 等待状态(WATING)

(1/2) 情况1:线程本身手动调用 wait() 方法

执行 wait() 方法的那个线程,在执行了该方法后,会释放锁,并进入等待状态WAITING(只有别的线程调用notifyAll()方法才能唤醒该线程)

测试程序:

public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        Thread test1 = new Thread(() -> {
            enter();
        });
        Thread test2 = new Thread(() -> {
            enter();
        });
        test1.setName("test1");
        test2.setName("test2");
        test1.start();
        test2.start();
    }
    
    private synchronized static void enter() {
        System.out.printf("线程[%s]进来了,它将执行以下耗时的业务操作\n", Thread.currentThread().getName());
        for (long j = 0L; j < 100000000000L; j++) {
            if (j == 30000000000L) {
                System.out.printf("线程[%s]执行到一半时,调用了wait()方法\n", Thread.currentThread().getName());
                try {
                    // 冷知识1:wait()方法必须通过同步监视器对象(monitor)来调用
                    // 冷知识2:静态的同步方法中,monitor默认是当前类的class对象,即Test3.class
                    // 所以这里必须通过"Test3.class"来调用wait()方法,否则会报错
                    Test3.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.printf("线程[%s]执行完毕\n", Thread.currentThread().getName());
    }
}

运行结果:

# 控制台打印信息:
线程[test1]进来了,它将执行以下耗时的业务操作
线程[test1]执行到一半时,调用了wait()方法
线程[test2]进来了,它将执行以下耗时的业务操作  # 此处说明了wait()方法会释放锁

(以上是目前为止已打印的结果)

此时,第一个线程(test1)在方法中执行了wait()后,进入了WAITING状态。

在这里插入图片描述

(2/2) 情况2:某线程调用其他线程的join()方法

测试程序:

public class Test3 {
    
    private static Thread test1;
    private static Thread test2;
    
    public static void main(String[] args) throws InterruptedException {
        test1 = new Thread(() -> {
            System.out.println("线程test1开始执行");
            for (long j = 0L; j < 100000000000L; j++) {
                if (j == 30000000000L) {
                    System.out.println("线程test1执行到一半时,调用了t2的join()方法");
                    try {
                        test2.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            System.out.println("线程test1执行完毕");
        });
        test2 = new Thread(() -> {
            System.out.println("线程test2开始执行");
            for (long j = 0L; j < 100000000000L; j++) {
                
            }
            System.out.println("线程test2执行完毕");
        });
        test1.setName("test1");
        test2.setName("test2");
        test1.start();
        test2.start();
    }
}

运行结果:

# 控制台打印信息:
线程test1开始执行
线程test2开始执行
线程test1执行到一半时,调用了test2的join()方法 

(以上是目前为止已打印的结果)

由于线程test1调用了test2.join(),因此线程test1会暂停执行,只有test2执行完毕后才被唤醒。
此时test1处在等待状态WAITING

在这里插入图片描述

2.2.5 计时等待状态(TIMED_WAITING)

(1/3) 情况1:线程本身手动调用 wait(time) 方法

和 2.2.4 等待状态(WAITING)情况1 其实是类似的,只是调用的方法由wait()变成了wait(time)

测试程序:

public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        Thread test1 = new Thread(() -> {
            enter();
        });
        Thread test2 = new Thread(() -> {
            enter();
        });
        test1.setName("test1");
        test2.setName("test2");
        test1.start();
        test2.start();
    }
    
    private synchronized static void enter() {
        System.out.printf("线程[%s]进来了,它将执行以下耗时的业务操作\n", Thread.currentThread().getName());
        for (long j = 0L; j < 100000000000L; j++) {
            if (j == 30000000000L) {
                System.out.printf("线程[%s]执行到一半时,调用了wait(time)方法\n", Thread.currentThread().getName());
                try {
                    // wait()方法必须通过同步监视器(monitor)来调用
                    // 冷知识:静态的同步方法中,monitor默认是当前类的class对象,即Test3.class
                    // 所以这里必须通过"Test3.class"来调用wait()方法,否则会报错
                    Test3.class.wait(10 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.printf("线程[%s]执行完毕\n", Thread.currentThread().getName());
    }
}

运行结果:

# 控制台打印信息:
线程[test1]进来了,它将执行以下耗时的业务操作
线程[test1]执行到一半时,调用了wait(time)方法
线程[test2]进来了,它将执行以下耗时的业务操作

(以上是目前为止已打印的结果)

此时test1线程进入了计时等待状态(TIMED_WAITING

在这里插入图片描述

(2/3) 情况2:某线程调用其他线程的join(time)方法

和 2.2.4 等待状态(WAITING)情况2 其实是类似的,只是调用的方法由join()变成了join(time)

测试程序:

public class Test3 {

    private static Thread test1;
    private static Thread test2;

    public static void main(String[] args) throws InterruptedException {
        test1 = new Thread(() -> {
            System.out.println("线程test1开始执行");
            for (long j = 0L; j < 100000000000L; j++) {
                if (j == 30000000000L) {
                    System.out.println("线程test1执行到一半时,调用了test2的join(time)方法");
                    try {
                        test2.join(10 * 1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            System.out.println("线程test1执行完毕");
        });
        test2 = new Thread(() -> {
            System.out.println("线程test2开始执行");
            for (long j = 0L; j < 100000000000L; j++) {

            }
            System.out.println("线程test2执行完毕");
        });
        test1.setName("test1");
        test2.setName("test2");
        test1.start();
        test2.start();
    }
}

运行结果:

# 控制台打印信息:
线程test1开始执行
线程test2开始执行
线程test1执行到一半时,调用了test2的join(time)方法 

(以上是目前为止已打印的结果)

由于线程test1调用了test2.join(time)test1会处在计时等待状态TIMED_WAITING

在这里插入图片描述

(3/3) 情况3:某线程调用sleep(time)方法

线程调用Thread.sleep(time)方法后,也会进入计时等待状态(TIMED_WAITING

测试程序:

public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        Thread test1 = new Thread(() -> {
            try {
                System.out.println("年轻人身体就是好,一进来工作时倒头就睡");
                Thread.sleep(100 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        test1.setName("test1");
        test1.start();
    }
}

运行结果:

# 控制台打印信息:
年轻人身体就是好,一进来工作时倒头就睡

在这里插入图片描述

2.2.6 终止状态(TERMINATED)

当一个线程逻辑已执行完毕,或出现了异常而终止的时候,此时线程状态应该是 TERMINATED

这个状态下的线程用arthas工具获取不到,所以就只能用代码获取了。

测试程序:

public class Test3 {
    
    public static void main(String[] args) throws InterruptedException {
        Thread test1 = new Thread(() -> {
            System.out.println("test1执行过程出现异常");
            int a = 1 / 0;
        });
        Thread test2 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                int a = 666;
            }
            System.out.println("test2任务已执行完毕");
        });
        test1.setName("test1");
        test2.setName("test2");
        test1.start();
        test2.start();
        Thread.sleep(1000);
        System.out.printf("线程[%s]的状态: %s\n", test1.getName(), test1.getState());
        System.out.printf("线程[%s]的状态: %s\n", test2.getName(), test2.getState());
    }
}

运行结果:

# 控制台打印信息:
test1执行过程出现异常
test2任务已执行完毕
Exception in thread "test1" java.lang.ArithmeticException: / by zero
	at draft.Test3.lambda$main$0(Test3.java:8)
	at java.lang.Thread.run(Thread.java:748)
线程[test1]的状态: TERMINATED
线程[test2]的状态: TERMINATED

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

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

相关文章

宝塔如何部署springboot前后端分离项目

一&#xff1a;准备工作 1.阿里云购买一个centos的服务器 2.环境准备&#xff1a; 2.1 jdk1.8 2.2 nginx 2.3 mysql 2.4 redis 2.5tomcat 这里根据网上的教学很好安装如果是宝塔的话除了redis都可以一键安装 软件商城安装这三个即可 数据库也是可以直接可视化创建并且导入sq…

计蒜客T1115——字符串判等

水题不解释&#xff0c;考研复习压力偶尔写一道换换心情还不错~ 这里有一个比较有趣的知识点&#xff0c;对于同时输入多个字符串时还要允许空格的输入&#xff0c;那么普通的cin函数就不能满足要求了&#xff0c;这里采用getline函数解决&#xff0c;如下&#xff1a; string …

PS 2023 安装选项页面显示不全

文章目录 PS 2023 安装选项页面显示不全解决办法 PS 2023 安装选项页面显示不全 解决办法 按住Tab键&#xff0c;点击该安装选项页面即可&#xff0c;如下如所示&#xff1a;

【Django】招聘面试管理01 创建项目运行项目

文章目录 前言一、创建项目二、运行项目三、访问后台管理页面四、配置项总结 前言 跟着视频学一学&#xff0c;记录一下。 一、创建项目 照着步骤创建虚拟环境&#xff0c;安装Django等依赖包&#xff0c;创建项目&#xff1a;【Django学习】01 项目创建、结构及命令 > d…

【Linux】带你了解高级IO

​&#x1f320; 作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《学会Linux》 &#x1f387; 座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录 &#x1f449;IO的基本…

【Rust】Rust学习 第七章使用包、Crate和模块管理不断增长的项目

目前为止&#xff0c;我们编写的程序都在一个文件的一个模块中。伴随着项目的增长&#xff0c;你可以通过将代码分解为多个模块和多个文件来组织代码。一个包可以包含多个二进制 crate 项和一个可选的 crate 库。伴随着包的增长&#xff0c;你可以将包中的部分代码提取出来&…

ArcGIS Pro实践技术应用暨基础入门、制图、空间分析、影像分析、三维建模、空间统计分析与建模、python融合、案例应用

GIS是利用电子计算机及其外部设备&#xff0c;采集、存储、分析和描述整个或部分地球表面与空间信息系统。简单地讲&#xff0c;它是在一定的地域内&#xff0c;将地理空间信息和 一些与该地域地理信息相关的属性信息结合起来&#xff0c;达到对地理和属性信息的综合管理。GIS的…

动力节点新版Docker实用教程,从入门到高阶一套搞定

Docker是一种轻量级的容器化平台&#xff0c;它可以大大简化应用程序的在不同环境中的部署、管理与扩展。 学习Docker可以让你更好地了解和应用容器化技术&#xff0c;实现软件运环境的快速部署和管理。 如何系统全面掌握Docker&#xff1f; 推荐一套天花板级Docker教程&#x…

前端页面--视觉差效果

代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><link rel"stylesheet" href"https://un…

Explorable Tone Mapping Operators

Abstract 色调映射在高动态范围(HDR)成像中起着至关重要的作用。 它的目的是在有限动态范围的介质中保存HDR图像的视觉信息。 虽然许多工作已经提出从HDR图像中提供色调映射结果&#xff0c;但大多数只能以一种预先设计的方式进行色调映射。 然而&#xff0c;声调映射质量的主…

lc1.两数之和

暴力解法&#xff1a;两个for循环&#xff0c;寻找和为target的两个数的索引 时间复杂度&#xff1a;O(n2) 空间复杂度&#xff1a;O(1) 哈希表&#xff1a;遍历数组&#xff0c;将nums数组的数和索引分别存储在map的key和value中&#xff0c;一边遍历&#xff0c;一边寻找是…

多语言多用户购物网站--海外仓进出口贸易平台开发

搭建一个多语言多用户购物网站需要具备一定的技术和资源&#xff0c;下面是一个大致的步骤&#xff1a; 1.确定需求&#xff1a;首先确定购物网站的功能需求&#xff0c;包括商品展示、购物车、订单管理、支付方式、物流管理等。同时还需要考虑到海外仓进出口贸易的特点&#…

如何使用Pycharm 快速搭建 Django 项目 (分享详细图文教程)

1. 准备工作 在开始创建Django项目之前&#xff0c;需要先确保已经安装了Python和Pycharm。并且python中已经安装好了Django依赖。 1安装python&#xff08;这里我安装使用的是python3.11.4稳定版本&#xff09; 官网下载太慢了这里直接贴网盘下载连接了&#xff0c;一起贴出py…

java版工程项目管理系统源码+系统管理+系统设置+项目管理+合同管理+二次开发em

​ 鸿鹄工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离构建工程项目管理系统 1. 项目背景 一、随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大。为了提高工程管理效率、减轻劳动强度、提高信息处理速度和准确性&#xff0c;公司对内部…

解决vue3中不能使用vue-simple-uploader

vue-simple-uploader本身是基于vue2的&#xff0c;直接npm i vue-simple-uploader -S下载下来版本的是0.7.6。在vue3中无法使用会报错。 解决&#xff1a;使用next安装接下来要发布的版本就会下载1.0.1版本&#xff0c;即可使用vue3 npm i vue-simple-uploadernext -S 注意&…

基于Echarts的大数据可视化模板:智慧物流管理

目录 引言物流管理的重要性大数据可视化在解决物流管理挑战中的作用智慧物流概述定义智慧物流的概念和特点智慧物流的关键技术和平台风险管理和预测:交通拥堵情况和风险预警Echarts与大数据可视化Echarts库以及其在大数据可视化领域的应用优势开发过程和所选设计方案模板如何满…

企业计算机服务器中了locked勒索病毒怎么办,如何预防勒索病毒攻击

计算机服务器是企业的关键信息基础设备&#xff0c;随着计算机技术的不断发展&#xff0c;企业的计算机服务器也成为了众多勒索者的攻击目标&#xff0c;勒索病毒成为当下计算机服务器的主要攻击目标。近期&#xff0c;我们收到很多企业的求助&#xff0c;企业的服务器被locked…

linux程序保护机制gcc编译选项

预备知识&#xff1a; 计算机内存的结构通常包括以下几个主要部分&#xff1a; 1.代码段(Code Segment)&#xff1a;也称为文本段&#xff0c;存储程序的可执行指令。代码段是被标记为可执行的&#xff0c;程序从代码段中获取指令并执行。 2.数据段(Data Segment)&#xff1a…

聚焦智慧医疗-RK3566智能主板助力POCT领域

POCT又称即时检验&#xff0c;是IVD(体外诊断)行业的一个细分领域&#xff0c;是指在采样现场即刻进行的快速诊断&#xff0c;省去了标本在实验室检验的复杂处&#xff0c;利用便携式分析仪器及配套试剂快速得到检测结果的一种检测方式。常见的POCT设备有生化分析仪、免疫分析仪…

Netty的ByteToMessageDecoder分析

说明 io.netty.handler.codec.ByteToMessageDecoder是一个解码器&#xff0c;从字节数据转换为其它类型的数据。ByteToMessageDecoder内部有一个累加器&#xff0c;将收到的字节读出来累积到一个ByteBuf中。ByteToMessageDecoder是个抽象类型&#xff0c;其中抽象函数decode(C…