面试题:说说Java线程的状态及转换

文章目录

  • 为何要了解Java线程状态
  • Java线程状态转换图
  • Java线程有哪些状态?
  • 关于wait()放在while循环的疑问
  • BLOCKED 和 WAITING 状态的区别和联系


为何要了解Java线程状态

线程是 JVM 执行任务的最小单元,理解线程的状态转换是理解后续多线程问题的基础。

Java线程状态转换图

在这里插入图片描述

Java线程有哪些状态?

在 JVM 运行中,线程一共有 NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED 六种状态,这些状态对应 Thread.State 枚举类中的状态。

Thread.State枚举源码:

为方便阅读,在此去掉了文档注释

public enum State {NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED;
}

在给定的时间点,线程只能处于这些状态中的一种状态。这些状态是不反映任何操作系统线程状态的虚拟机状态。

NEW,TERMINATED

这两个状态比较好理解,当创建一个线程后,还没有调用start()方法时,线程处在 NEW 状态,线程完成执行,退出后变为TERMINATED终止状态。

RUNNABLE

运行 Thread 的 start 方法后,线程进入 RUNNABLE 可运行状态

/*** 程序目的:观察线程的各种状态*/
class MyThread extends Thread {@Overridepublic void run() {System.out.printf("%s线程运行\n", Thread.currentThread().getName());}
}/*** 分别观察创建线程后、start()后、和线程退出后的线程状态。* 其中Thread.sleep(50);是为了等待线程执行完*/
public class ThreadStateDemo {public static void main(String[] args) throws InterruptedException {MyThread myThread = new MyThread();System.out.printf("创建线程后,线程的状态为:%s\n", myThread.getState());myThread.start();System.out.printf("调用start()方法后线程的状态为:%s\n", myThread.getState());// 休眠50毫秒,等待MyThread线程执行完Thread.sleep(50);System.out.printf("再次打印线程的状态为:%s\n", myThread.getState());}
}

输出结果:

创建线程后,线程的状态为:NEW
调用start()方法后线程的状态为:RUNNABLE
Thread-0线程运行
再次打印线程的状态为:TERMINATED

我们可以看到,输出结果符合预期。

  • 在刚创建完线程后,状态为NEW
  • 调用了start()方法后线程的状态变为:RUNNABLE。
  • 然后,我们看到了run()方法的执行,这个执行,是在主线程main打印了调用start()方法后线程的状态为:RUNNABLE输出后执行的。
  • 随后,我们让main线程休眠了50毫秒,等待MyThread线程退出
  • 最后再打印MyThread线程的状态,为TERMINATED。

BLOCKED

如图左侧所示,在运行态中的线程进入 synchronized 同步块或者同步方法时,如果获取锁失败,则会进入到 BLOCKED 状态。当获取到锁后,会从 BLOCKED 状态恢复到就绪状态。

import lombok.extern.slf4j.Slf4j;/*** 程序目的:观察线程的BLOCKED状态*/
@Slf4j
public class ThreadBlockedStateDemo {public static void main(String[] args) {Thread threadA = new Thread(() -> method01(), "A-Thread");Thread threadB = new Thread(() -> method01(), "B-Thread");threadA.start();threadB.start();log.info("线程A的状态为:{}", threadA.getState());log.info("线程B的状态为:{}", threadB.getState());}/*** 停顿10毫秒、模拟方法执行耗时*/public static synchronized void method01() {log.info("[{}]:开始执行主线程的方法", Thread.currentThread().getName());try {Thread.sleep(10);}catch (InterruptedException e) {e.printStackTrace();}log.info("[{}]:主线程的方法执行完毕", Thread.currentThread().getName());}
}

输出结果:

2020-06-26 20:32:15.404 [A-Thread] INFO  com.hua.threadtest.state.ThreadBlockedStateDemo - [A-Thread]:开始执行主线程的方法
2020-06-26 20:32:15.404 [main    ] INFO  com.hua.threadtest.state.ThreadBlockedStateDemo - 线程A的状态为:RUNNABLE
2020-06-26 20:32:15.407 [main    ] INFO  com.hua.threadtest.state.ThreadBlockedStateDemo - 线程B的状态为:BLOCKED
2020-06-26 20:32:15.417 [A-Thread] INFO  com.hua.threadtest.state.ThreadBlockedStateDemo - [A-Thread]:主线程的方法执行完毕
2020-06-26 20:32:15.418 [B-Thread] INFO  com.hua.threadtest.state.ThreadBlockedStateDemo - [B-Thread]:开始执行主线程的方法
2020-06-26 20:32:15.430 [B-Thread] INFO  com.hua.threadtest.state.ThreadBlockedStateDemo - [B-Thread]:主线程的方法执行完毕

A线程优先获得到了锁,状态为RUNNABLE,这时,B线程处于BLOCKED状态。

当A线程执行完毕后,B线程执行对应方法。

WAITING,TIMED_WAITING

如图右侧所示,运行中的线程还会进入等待状态,这两个等待一个是有超时时间的等待,例如调用 Object.wait、Thread.join 等;另外一个是无超时的等待,例如调用 Thread.join 或者 Locksupport.park等。这两种等待都可以通过 notify 或 unpark 结束等待状态并恢复到就绪状态。

官方文档说明为:

A thread in the waiting state is waiting for another thread to perform a particular action. For example, a thread that has called Object.wait() on an object is waiting for another thread to call Object.notify() or Object.notifyAll() on that object. A thread that has called Thread.join() is waiting for a specified thread to terminate.

处于等待状态的线程正在等待另一个线程执行特定的操作。

接下来我们来模拟一下线程的WAITING状态:

import lombok.extern.slf4j.Slf4j;/*** <pre>* 程序目的:观察线程的WAITING状态* 模拟:只有一个售票窗口的售票厅,有两个粉丝都想买票。* 如果没有票,他们就继续等待、如果有票,则买票、然后离开售票厅。* 其中,工作人员会补票,补票之后,粉丝就可以买到票了。* </pre>*/
@Slf4j
public class ThreadWaitingStateDemo {public static void main(String[] args) throws InterruptedException {Ticket ticket = new Ticket();Thread threadA = new Thread(() -> {synchronized (ticket) {while (ticket.getNum() == 0) {try {ticket.wait();}catch (InterruptedException e) {e.printStackTrace();}}ticket.buy();}}, "粉丝A");Thread threadB = new Thread(() -> {synchronized (ticket) {while (ticket.getNum() == 0) {try {ticket.wait();}catch (InterruptedException e) {e.printStackTrace();}}ticket.buy();}}, "粉丝B");threadA.start();threadB.start();// 确保A和B线程都运行起来Thread.sleep(10);log.info("粉丝A线程的状态为:{}", threadA.getState());log.info("粉丝B线程的状态为:{}", threadB.getState());Thread employeeThread = new Thread(() -> {synchronized (ticket) {if (ticket.getNum() == 0) {ticket.addTickt();ticket.notifyAll();}}}, "补票员");employeeThread.start();}}@Slf4j
class Ticket {/*** 票的张数*/private int num = 0;public int getNum() {return num;}public void addTickt() {try {Thread.sleep(2_000);}catch (InterruptedException e) {e.printStackTrace();}log.info("补充票");this.num += 2;}/*** 停顿10毫秒、模拟方法执行耗时*/public void buy() {log.info("[{}]:购买了一张票", Thread.currentThread().getName());log.info("[{}]:退出售票厅", Thread.currentThread().getName());}
}

输出:

2020-06-26 21:26:37.938 [main    ] INFO  com.hua.threadtest.state.ThreadWaitingStateDemo - 粉丝A线程的状态为:WAITING
2020-06-26 21:26:37.945 [main    ] INFO  com.hua.threadtest.state.ThreadWaitingStateDemo - 粉丝B线程的状态为:WAITING
2020-06-26 21:26:39.948 [补票员     ] INFO  com.hua.threadtest.state.Ticket - 补充票
2020-06-26 21:26:39.949 [粉丝B     ] INFO  com.hua.threadtest.state.Ticket - [粉丝B]:购买了一张票
2020-06-26 21:26:39.949 [粉丝B     ] INFO  com.hua.threadtest.state.Ticket - [粉丝B]:退出售票厅
2020-06-26 21:26:39.949 [粉丝A     ] INFO  com.hua.threadtest.state.Ticket - [粉丝A]:购买了一张票
2020-06-26 21:26:39.949 [粉丝A     ] INFO  com.hua.threadtest.state.Ticket - [粉丝A]:退出售票厅

当修改ticket.wait();为ticket.wait(10);后,输出结果如下:

2020-06-26 21:27:10.704 [main    ] INFO  com.hua.threadtest.state.ThreadWaitingStateDemo - 粉丝A线程的状态为:TIMED_WAITING
2020-06-26 21:27:10.709 [main    ] INFO  com.hua.threadtest.state.ThreadWaitingStateDemo - 粉丝B线程的状态为:TIMED_WAITING
2020-06-26 21:27:12.714 [补票员     ] INFO  com.hua.threadtest.state.Ticket - 补充票
2020-06-26 21:27:12.714 [粉丝B     ] INFO  com.hua.threadtest.state.Ticket - [粉丝B]:购买了一张票
2020-06-26 21:27:12.714 [粉丝B     ] INFO  com.hua.threadtest.state.Ticket - [粉丝B]:退出售票厅
2020-06-26 21:27:12.715 [粉丝A     ] INFO  com.hua.threadtest.state.Ticket - [粉丝A]:购买了一张票
2020-06-26 21:27:12.715 [粉丝A     ] INFO  com.hua.threadtest.state.Ticket - [粉丝A]:退出售票厅

关于wait()放在while循环的疑问

为什么ticket.wait();要放在while (ticket.getNum() == 0)代码块中呢?既然这行代码时让线程等待着,那使用if不就行了?

我们设想一下,如果使用if,则在线程被唤醒后,会继续往下执行,不再判断条件是否符合,这时还是没有票,粉丝也就购买不到票了。

我们看一下Object.wait()的官方doc说明:

As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop:synchronized (obj) {while (<condition does not hold>)obj.wait();... // Perform action appropriate to condition}

在一个参数版本中(wait方法),中断和虚假的唤醒是可能的,这个方法应该总是在循环中使用。

我们再继续看Object.wait(long timeout)的文档说明:

A thread can also wake up without being notified, interrupted, or
timing out, a so-called spurious wakeup. While this will rarely occur
in practice, applications must guard against it by testing for the
condition that should have caused the thread to be awakened, and
continuing to wait if the condition is not satisfied. In other words,
waits should always occur in loops

线程也可以在没有通知、中断或超时的情况下被唤醒,这就是所谓的假唤醒。虽然这种情况在实践中很少发生,但应用程序必须通过测试导致线程被唤醒的条件来防止这种情况发生,如果条件不满足,则继续等待。换句话说,等待应该总是在循环中发生

所以,为了避免很少发生的假唤醒出现时程序发生不可预知的错误,建议把wait()调用放在循环语句中。这样就算被假唤醒,也有条件语句的限制。

这也是为何wait要放在循环语句中的一个原因。

BLOCKED 和 WAITING 状态的区别和联系

表:处于等待状态的各种细分状态对比

在这里插入图片描述

简单来说,处于BLOCKED状态的线程,还是在竞争锁的,一旦cpu有时间,它竞争到了锁、就会执行。

但是WAITING状态的线程则不去竞争锁,需要等待被动通知、或者自己定的闹钟(等待时间)到了、再去竞争锁。

一图胜千言,在此引用一张国外一位大牛画的图:

图:线程转换-详细

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

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

相关文章

课题学习(六)----安装误差校准、实验方法

一、 安装误差校准 1.1 数学模型 在实际情况下&#xff0c;即使努力尝试使三轴加速度计和三轴磁通门正交&#xff0c;也不可能保证坐标轴的正交和安装的准确居中。无论采用何种解法&#xff0c;都会导致最终解的误差。因此&#xff0c;要想提高测量精度&#xff0c;就必须开发…

win10取消ie浏览器自动跳转edge浏览器

建议大家看完整篇文章再作操作 随着windows10 日渐更新&#xff0c;各种不同的操作&#xff0c;规避IE浏览器跳转Edge浏览器的问题 算了&#xff0c;找了台云机装的server 有自带的IE 1.&#xff08;失败&#xff09;思路 协助Edge浏览器 管理员身份打开 PowerShell 一般e…

华为云Stack的学习(九)

十、华为云Stack灾备服务介绍 1.云硬盘备份VBS 云硬盘备份服务&#xff08;VBS&#xff0c;Volume Backup Service&#xff09;可为云硬盘&#xff08;EVS&#xff0c;Elastic Volume Service&#xff09;创建备份&#xff0c;利用备份数据恢复云硬盘&#xff0c;最大限度保障…

PyTorch 深度学习之加载数据集Dataset and DataLoader(七)

1. Revision: Manual data feed 全部Batch&#xff1a;计算速度&#xff0c;性能有问题 1 个 &#xff1a;跨越鞍点 mini-Batch:均衡速度与性能 2. Terminology: Epoch, Batch-Size, Iteration DataLoader: batch_size2, sheffleTrue 3. How to define your Dataset 两种处…

【Zabbix】Zabbix学习笔记

现在Zabbix Server存在的问题&#xff1a; 问题1&#xff1a; Zabbix server: Utilization of discoverer processes over 75% 问题2&#xff1a; Zabbix server: Utilization of icmp pinger processes over 75% 优化的解决办法是修改配置文件把Discovery和Pinger进程数量调大…

MySQL MVCC详细介绍

MVCC概念 MVCC(Multi-Version Concurrency Control) 多版本并发控制&#xff0c;是一种并发控制机制,用于处理数据库中的并发读写操作&#xff0c;它通过在每个事务中创建数据的快照&#xff0c;实现了读写操作的隔离性&#xff0c;从而避免了读写冲突和数据不一致的问题。 M…

JAVA设计模式-适配器模式

一.概念 ​ 将一个接口转换成客户希望的另一个接口&#xff0c;使接口不兼容的那些类可以一起工作&#xff0c;其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式&#xff0c;也可以作为对象结构型模式。 ​ 在适配器模式中&#xff0c;我们通过增加一个新的适配器…

SpringBootCms

SpringBootCMS&#xff0c;极速开发&#xff0c;动态添加字段&#xff0c;自定义标签&#xff0c;动态创建数据库表并crud数据&#xff0c;数据库备份、还原&#xff0c;动态添加站点(多站点功能)&#xff0c;一键生成模板代码&#xff0c;让您轻松打造自己的独立网站&#xff…

Linux文件-内存映射mmap

mmap定义为&#xff1a;Linux通过将一个虚拟内存区域与一个磁盘上的对象(object)关联起来&#xff0c;以初始化这个虚拟内存区域的内容&#xff0c;这个过程称为内存映射(memory mapping)。 在LINUX中我们可以使用mmap用来在进程虚拟内存地址空间中分配地址空间&#xff0c;创…

基于Vue构建的快速开发框架

一、Vue结合低代码 "低代码"是一种快速开发应用的方法&#xff0c;它使开发者能够通过图形界面和预构建的块进行设计和构建&#xff0c;而不是手动编写大量的代码。这种方法被广泛用于快速应用开发、移动应用开发、业务流程管理和数据库应用开发等领域。 Vue.js 是一…

leetCode 115.不同的子序列 动态规划 + 滚动数组(优化)

给你两个字符串 s 和 t &#xff0c;统计并返回在 s 的 子序列 中 t 出现的个数&#xff0c;结果需要对 10^9 7 取模 示例 1&#xff1a; 输入&#xff1a;s "rabbbit", t "rabbit" 输出&#xff1a;3 解释&#xff1a;如下所示, 有 3 种可以从 s 中得…

关于:未同意隐私政策,应用获取ANDROID ID问题2

一、环境 Unity2018 4.21f1、Android Studio、Windows10 二、问题描述 在发布应用到华为应用市场时&#xff0c;提示“在用户同意隐私政策前&#xff0c;您的应用获取了用户的ANDROID ID&#xff0c;不符合华为应用市场审核标准。” 如果你想去掉获取ANDROID ID的代码可以参…

Webmin远程命令执行漏洞复现报告

漏洞编号 CVE-2019-15107 漏洞描述 Webmin是一个基于Web的系统配置工具&#xff0c;用于类Unix系统。密码重置页面中存在此漏洞&#xff0c;允许未经身份验证的用户通过简单的 POST 请求执行任意命令。 影响版本 Webmin<1.920 漏洞评级 严重 利用方法&#xff08;利…

STM32MP157按键中断实验

按键配置 #include "key_it.h" #include "stm32mp1xx_gpio.h" #include "stm32mp1xx_gic.h" #include "stm32mp1xx_exti.h" #include "stm32mp1xx_rcc.h"void key_it_config() {/* RCC使能GPIOF时钟 */RCC->MP_AHB4ENSE…

ubuntu20.04 nerf Instant-ngp (下) 复现,自建数据集,导出mesh

参考链接 Ubuntu20.04复现instant-ngp&#xff0c;自建数据集&#xff0c;导出mesh_XINYU W的博客-CSDN博客 GitHub - NVlabs/instant-ngp: Instant neural graphics primitives: lightning fast NeRF and more youtube上的一个博主自建数据集 https://www.youtube.com/watch…

es6(二)——常用es6说明

ES6的系列文章目录 es6&#xff08;一&#xff09;——var和let和const的区别 文章目录 ES6的系列文章目录一、变量的结构赋值1.数组的结构赋值2.对象的结构赋值 二、模板字符串三、扩展运算符1.字符串的使用2.数组的使用 四、箭头函数1.普通函数的定义2.箭头函数的定义3.箭头…

uniapp app获取keystore等一系列常用数据

https://blog.csdn.net/deepdfhy/article/details/88698492 参考文章 一、获取安卓证书keystore的SHA1和SHA256值 参数上面引用链接 window r : $ cmd $ D: 进入D盘 $ keytool -genkey -alias testalias -keyalg RSA -keysize 2048 -validity 36500 -keystore 项目名称.ke…

[GXYCTF2019]BabyUpload - 文件上传+绕过(后缀文件类型文件内容.htaccess)

[GXYCTF2019]BabyUpload 解题流程 解题流程 1、上传一句话&#xff0c;提示“后缀不允许ph” 2、修改后缀为jpg&#xff0c;提示“上传类型也太露骨了吧&#xff01;” 3、修改类型为image/jpeg&#xff0c;提示“诶&#xff0c;别蒙我啊&#xff0c;这标志明显还是php啊” 4、…

【Linux 安装Kibana 及 Es 分词器安装】

一、客户端Kibana安装 Kibana是一个开源分析和可视化平台&#xff0c;旨在与Elasticsearch协同工作。参考文档 1. 下载并解压缩Kibana 下载路径 选择的版本是和 ElasticSearch 对应&#xff08;7.17.3&#xff09; 下载后上传到Linux 系统中&#xff0c;并放在 /root/ 下&a…

Linux编译FFmpeg

Linux编译FFmpeg 1. 下载FFmpeg源码 FFmpeg源码下载地址&#xff1a;http://ffmpeg.org/download.html 在下面选择版本 2. 解压并创建生成目录 tar xvf ffmpeg-snapshot.tar.bz2 // 解压下载的FFmpeg源码 makedir /root/ffmpeg // 创建生成目录3. 编译FFmpeg 进入FF…