Java面试必考题之线程的生命周期,结合源码,透彻讲解!

写在开头

在前面的几篇博客里,我们学习了Java的多线程,包括线程的作用、创建方式、重要性等,那么今天我们就要正式踏入线程,去学习更加深层次的知识点了。

第一个需要学的就是线程的生命周期,也可以将之理解为线程的几种状态,以及互相之间的切换,这几乎是Java多线程的面试必考题,每一年都有大量的同学,因为这部分内容回答不够完美而错过高薪,今天我们结合源码,好好来聊一聊。

线程的生命周期

所谓线程的生命周期,就是从它诞生到消亡的整个过程,而不同的编程语言,对线程生命周期的封装是不同的,在Java中线程整个生命周期被分为了六种状态,我们下面就来一起学习一下。

线程的6种状态

对于Java中线程的状态划分,我们其实要从两个方面去看,一是JVM层面,这是我们程序运行的核心,另一层面是操作系统层面,这是我们JVM能够运行的核心。为了更直观的分析,build哥列了一个对比图:
在这里插入图片描述
在操作系统层面,对于RUNNABLE状态拆分为(READY、RUNNING),那为什么在JVM层面没有分这么细致呢?

这是因为啊,在当下时分多任务操作系统架构下,线程的驱动是通过获取CPU时间片,而每个时间片的间隔非常之短(10-20ms),这就意味着一个线程在cpu上运行一次的时间在0.01秒,随后CPU执行权就会发生切换,在如此高频的切换下,JVM就没必要去区分READY和RUNNING了。

在Java的源码中也可以看到,确实只分了6种状态:

【源码解析1】

// Thread.State 源码
public enum State {//省略了每个枚举值上的注释NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED;
}
NEW(初始化状态)

我们通过new一个Thread对象,进行了初始化工作,这时的线程还没有被启动。

【代码示例1】

public class Test {public static void main(String[] args) {//lambda 表达式Thread thread = new Thread(() -> {});System.out.println(thread.getState());}
}
//执行结果:NEW

我们通过thread.getState()方法去获得当前线程所处在的状态,此时输出为NEW。

RUNNABLE(可运行状态)

对于这种状态的描述,我们来看一下Thread源码中如何说的:

/*** Thread state for a runnable thread. A thread in the runnable* state is executing in the Java virtual machine but it may* be waiting for other resources from the operating system* such as processor.*/

大致意思是线程处于RUNNABLE状态下,代表它可能正处于运行状态,或者正在等待CPU资源的分配。

那么我们怎样从NEW状态变为RUNNABLE呢?答案很简单,我们只需要调用start()方法即可!

【代码示例2】

public class Test {public static void main(String[] args) {//lambda 表达式Thread thread = new Thread(() -> {});thread.start();System.out.println(thread.getState());}
}
//执行结果:RUNNABLE
BLOCKED(阻塞状态)

当线程线程进入 synchronized 方法/块或者调用 wait 后(被 notify)重新进入 synchronized 方法/块,但是锁被其它线程占有,这个时候线程就会进入 BLOCKED(阻塞) 状态。这时候只有等到锁被另外一个线程释放,重新获取锁后,阻塞状态解除!

WAITING(无限时等待)

当通过代码将线程转为WAITING状态后,这种状态不会自动切换为其他状态,是一种无限时状态,直到整个线程接收到了外界通知,去唤醒它,才会从WAITING转为uRUNNABLE。
调用下面这 3 个方法会使线程进入等待状态:

  1. Object.wait():使当前线程处于等待状态直到另一个线程唤醒它;
  2. Thread.join():等待线程执行完毕,底层调用的是 Object 的 wait 方法;
  3. LockSupport.park():除非获得调用许可,否则禁用当前线程进行线程调度。
TIMED_WAITING(有限时等待)

与WAITING相比,TIMED_WAITING是一种有限时的状态,可以通过设置等待时间,没有外界干扰的情况下,达到指定等待时间后,自动终止等待状态,转为RUNNABLE状态。
调用如下方法会使线程进入超时等待状态:

  1. Thread.sleep(long millis):使当前线程睡眠指定时间;
  2. Object.wait(long timeout):线程休眠指定时间,等待期间可以通过notify()/notifyAll()唤醒;
  3. Thread.join(long millis):等待当前线程最多执行 millis 毫秒,如果 millis 为 0,则会一直执行;
  4. LockSupport.parkNanos(long nanos): 除非获得调用许可,否则禁用当前线程进行线程调度指定时间;
  5. LockSupport.parkUntil(long deadline):同上,也是禁止线程进行调度指定时间;
TERMINATED(终止状态)

线程正常执行结束,或者异常终止,会转变到 TERMINATED 状态。

线程状态的切换

上面的6种状态随着程序的运行,代码(方法)的执行,上下文的切换,也伴随着状态的转变。
在这里插入图片描述

NEW 到 RUNNABLE 状态

这一种转变比较好理解,我们通过new,初始化一个Thread对象后,这时就是处于线程的NEW状态,此时线程是不会获取CPU时间片调度执行的,只有在调用了start()方法后,线程彻底创建完成,进入RUNNABLE状态,等待操作系统调度执行!这种状态是NEW -> RUNNABLE的单向转变

RUNNABLE 与 BLOCKED 的状态转变

synchronized 修饰的方法、代码块同一时刻只允许一个线程执行,其他线程只能等待,等待的线程会从 RUNNABLE 转变到 BLOCKED 状态,当等待的线程获得 synchronized 隐式锁时,就又会从 BLOCKED 转变到 RUNNABLE 状态。我们通过一段代码示例看一下:

【代码示例3】

public class Test {public static void main(String[] args) {Thread thread1 = new Thread(() -> {testMethod();});Thread thread2 = new Thread(() -> {testMethod();});thread1.start();thread2.start();System.out.println(thread1.getName()+":"+thread1.getState());System.out.println(thread2.getName()+":"+thread2.getState());}// 同步方法争夺锁private static synchronized void testMethod() {try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}}
}

输出:

Thread-0:RUNNABLE
Thread-1:BLOCKED

代码中在主线程中创建了2个线程,线程中都调用了同步方法,随后去启动线程,因为CPU的执行效率较高,还没阻塞已经完成的打印,所以大部分时间里会输出两线程均为RUNNABLE状态;

当CPU效率稍低时,就会呈现上述结果,thread1启动后进入RUNNABLE状态,并且获得了同步方法,这是thread2启动后,调用的同步方法锁已经被占用,它作为等待的线程会从 RUNNABLE 转变到 BLOCKED 状态,待到thread1同步方法执行完毕,释放synchronized锁后,thread2获得锁,从BLOCKED转为RUNNABLE状态。

RUNNABLE 与 WAITING 的状态转变

1、获得 synchronized 隐式锁的线程,调用无参数的 Object.wait() 方法,状态会从 RUNNABLE 转变到 WAITING;调用 Object.notify()、Object.notifyAll() 方法,线程可能从 WAITING 转变到 RUNNABLE 状态。

2、调用无参数的 Thread.join() 方法。join() 是一种线程同步方法,如有一线程对象 Thread t,当调用 t.join() 的时候,执行代码的线程的状态会从 RUNNABLE 转变到 WAITING,等待 thread t 执行完。当线程 t 执行完,等待它的线程会从 WAITING 状态转变到 RUNNABLE 状态。

3、调用 LockSupport.park() 方法,线程的状态会从 RUNNABLE 转变到 WAITING;调用 LockSupport.unpark(Thread thread) 可唤醒目标线程,目标线程的状态又会从 WAITING 转变为 RUNNABLE 状态。

RUNNABLE 与 TIMED_WAITING 的状态转变

这种与上面的很相似,只是在方法调用和参数上有细微差别,因为,TIMED_WAITING 和 WAITING 状态的区别,仅仅是调用的是超时参数的方法。
转变方法在上文中已经提到了,这里以sleep(time)为例,写一个测试案例:

【代码示例4】

public class Test {public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {testMethod();});Thread thread2 = new Thread(() -> {// testMethod();});thread1.start();Thread.sleep(1000L);thread2.start();System.out.println(thread1.getName()+":"+thread1.getState());System.out.println(thread2.getName()+":"+thread2.getState());}// 同步方法争夺锁private static synchronized void testMethod() {try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}}
}

输出:

Thread-0:TIMED_WAITING
Thread-1:TERMINATED

这里面我们启动threa1后,让主线程休眠了1秒,这时thread1获得同步方法后,方法内部执行了休眠2秒的操作,因此它处于TIMED_WAITING状态,而thread2正常运行结束,状态处于TERMINATED(这个案例同样可以印证下面RUNNABLE到TERMINATED的转变)。

RUNNABLE 到 TERMINATED 状态

转变为TERMINATED状态,表明这个线程已经执行完毕,通常用如下几种情况:

  1. 线程执行完 run() 方法后,会自动转变到 TERMINATED 状态;
  2. 执行 run() 方法时异常抛出,也会导致线程终止;
  3. Thread类的 stop() 方法已经不建议使用。

总结

今天关于线程的6种状态就讲到这里啦,这是个重点知识点,希望大家能够铭记于心呀!

结尾彩蛋

如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏呀。原创不易,转载请联系Build哥!

在这里插入图片描述
如果您想与Build哥的关系更近一步,还可以关注“JavaBuild888”,在这里除了看到《Java成长计划》系列博文,还有提升工作效率的小笔记、读书心得、大厂面经、人生感悟等等,欢迎您的加入!

在这里插入图片描述

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

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

相关文章

Springboot jpa 查询排序Sort,分页Page使用报错

一.Sort 报错信息&#xff1a; “Sort(org.springframework.data.domain.Sort.Direction, java.util.List<java.lang.String>) has private access in org.springframework.data.domain.Sort” 原因&#xff1a; 使用Sort sort new Sort(Sort.Direction.DESC, "…

orm之SQLAlchemy

SQLAlchemy 1.介绍 SQLAlchemy是一个基于Python实现的ORM框架。该框架建立在 DB API之上&#xff0c;使用关系对象映射进行数据库操作&#xff0c;简言之便是&#xff1a;将类和对象转换成SQL&#xff0c;然后使用数据API执行SQL并获取执行结果。 pip3 install sqlalchemy组…

JVM运行时数据区——对象的实例化内存布局与访问定位

文章目录 1、对象的实例化1.1、创建对象的方式1.2、创建对象的步骤 2、对象的内存布局3、对象的访问定位3.1、对象访问的定位方式3.2、使用句柄访问3.3、使用指针访问 4、小结 平时大家经常使用new关键字来创建对象&#xff0c;那么我们创建对象的时候&#xff0c;怎么去和运行…

ABAP接口-RFC连接(ABAP TO ABAP)

目录 ABAP接口-RFC连接&#xff08;ABAP TO ABAP&#xff09;创建ABAP连接RFC函数的调用 ABAP接口-RFC连接&#xff08;ABAP TO ABAP&#xff09; 创建ABAP连接 事务代码&#xff1a;SM59 点击创建&#xff0c;填写目标名称&#xff0c;选择连接类型&#xff1a; 填写主机名…

Vue动态绑定样式

虽然经常使用动态绑定样式的方法&#xff0c;但有时候突然要用&#xff0c;一瞬间还是会有点困惑&#xff0c;决定记录一下&#xff0c;方便混乱的时候查阅。 绑定 HTML class 绑定对象 <div :class"{ active: isActive }"></div> <divclass"…

pycharm查看Tensor的完整数据

通常debug时&#xff0c;Tensor的数据呈现如下图&#xff0c;只显示开头几个值和结尾几个值&#xff0c;中间被省略&#xff1a; 解决方法&#xff1a; 右击想查看的数据&#xff0c;选择Evaluate Expression 输入如下命令&#xff0c;就会出现View as Array提示&#xff0c;…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:LoadingProgress)

用于显示加载动效的组件。 说明&#xff1a; 该组件从API Version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 无 接口 LoadingProgress() 创建加载进展组件。 从API version 9开始&#xff0c;该接口支持在ArkTS卡片中使…

2024年k8s最新版本使用教程

2024年k8s最新版本使用教程 3. YAML语言入门3.1 基本语法规则3.2 支持的数据结构3.3 其他语法 4 资源管理4.1 k8s资源查询4.2 资源操作命令4.3 资源操作方式4.3.1 命令行方式4.3.2 YAML文件方式 5 Namespace5.1 查看命名空间5.2 创建命名空间5.3 删除命名空间5.4 命名空间资源限…

API接口商品竞品价格监控,品牌维权

第一步&#xff1a;确定监控目标 在开始之前&#xff0c;需要明确监控哪些竞品及其相关属性&#xff0c;如产品名称、SKU、价格、促销信息等。此外&#xff0c;设定监控频率和关键绩效指标&#xff08;KPIs&#xff09;&#xff0c;例如价格变动幅度、变动频率等&#xff0c;以…

脚本自动化 设置快捷方式并设置为管理员运行

自动化创建快捷方式并设置为始终以管理员权限运行&#xff0c;可以通过编写批处理脚本来实现。以下是一个创建.bat批处理文件快捷方式并设置为管理员运行的示例脚本&#xff1a; batch echo off set SCRIPT_PATH"C:\Scripts\myScript.bat" set SHORTCUT_PATH"%…

TenantLineHandler 在 MyBatis Plus 中处理多租户场景

TenantLineHandler 在 MyBatis Plus 中通常用于处理多租户场景。多租户是指在一个软件实例中&#xff0c;能够同时处理多个不同的租户数据&#xff0c;并且保证数据之间的隔离性。在多租户应用中&#xff0c;通常需要在 SQL 查询中加入额外的条件&#xff0c;以确保每个租户只能…

计算机组成原理之机器:输入输出系统

计算机组成原理之机器&#xff1a;输入输出系统 笔记来源&#xff1a;哈尔滨工业大学计算机组成原理&#xff08;哈工大刘宏伟&#xff09; Chater3&#xff1a;输入输出系统 3.1 输入输出系统的发展概况 早期阶段 外部设备与主机之间采用分散连接&#xff0c;即每一个设备都…

什么是微隔离技术?

微隔离产生的背景 首先来看下南北向流量以及东西向流量的含义 南北向流量 指通过网关进出数据中心的流量&#xff0c;在云计算数据中心&#xff0c;处于用户业务虚拟机&#xff08;容器&#xff09;跟外部网络之间的流量&#xff0c;一般来说防火墙等安全设备部署在数…

数组的访问2

1.数组的索引 数组的索引即数组的下标&#xff0c;使用下标查找到当前位置的数组中的值&#xff08;注意&#xff1a;数组下标是从0开始的&#xff09; public class ArrayDemo2 {public static void main(String[] args) {// 数组的索引int[] age {12,22,31};// 1.根据索引…

python控制语句-1.2

目录 循环结构 while循环 for循环 循环结构练习-1 循环嵌套 循环结构练习-2 循环控制语句&#xff08;continue & break&#xff09; 循环结构 while循环 语法 Python 编程中 while 语句用于循环执行程序&#xff0c;即在某条件下&#xff0c;循环执行某段程序&am…

Spring依赖注入的方式

目录 1. Setter注入 2. 构造器注入 3. 字段注入 4. 方法注入 5. 接口注入 6. Lookup注入 7. 注解注入 Spring框架提供了多种依赖注入&#xff08;DI&#xff09;的方式来管理对象之间的依赖关系。以下是Spring支持的依赖注入方式&#xff1a; 1. Setter注入 定义&…

ChatGPT4.0直接出图功能上线!神奇一键画,AI让创作更轻松(chatgpt4.0直接出圖)

ChatGPT4.0直接出图功能上线&#xff01;神奇一键画&#xff0c;AI让创作更轻松(chatgpt4.0直接出圖) ChatGPT4.0的核心功能 ChatGPT4.0是OpenAI发布的最新版本的ChatGPT&#xff0c;相较于之前的版本有许多新增的功能和特点。其中最显著的功能是新增了直接出图功能&#xff…

Python打印Linux系统中最常用的linux命令之示例

一、Linux中的~/.bash_history文件说明&#xff1a; 该文件保存了linux系统中运行过的命令的历史。使用该文件来获取命令的列表&#xff0c;并统计命令的执行次数。统计时&#xff0c;只统计命令的名称&#xff0c;以不同参数调用相同的命令也视为同一命令。 二、示例代码&am…

【C语言步行梯】分支语句if...else、switch详谈

&#x1f3af;每日努力一点点&#xff0c;技术进步看得见 &#x1f3e0;专栏介绍&#xff1a;【C语言步行梯】专栏用于介绍C语言相关内容&#xff0c;每篇文章将通过图片代码片段网络相关题目的方式编写&#xff0c;欢迎订阅~~ 文章目录 什么是语句&#xff1f;引入分支语句&am…

GPT的磁盘管理

GPT分区工具&#xff1a;gdisk gdisk gdisk分区 GPT 128个主分区 [rootzutuanxue ~]# gdisk -l /dev/sdc [rootzutuanxue ~]# gdisk -l /dev/sdc 查看sdc信息 GPT fdisk (gdisk) version 1.0.3 ​ Partition table scan:MBR: MBR onlyBSD: not presentAPM: not presentGPT…