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

news2025/6/20 20:06:52

文章目录

  • 为何要了解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 {
 @Override
 public 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.coloradmin.cn/o/1086746.html

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

相关文章

加持智慧医疗,美格智能5G数传+智能模组让就医触手可及

智慧医疗将云计算、物联网、大数据、AI等新兴技术融合赋能医疗健康领域&#xff0c;是提高医疗健康服务的资源利用效率&#xff0c;创造高质量健康医疗的新途径。《健康中国2030规划纲要》把医疗健康提升到了国家战略层面&#xff0c;之后《“十四五”全面医疗保障规划》等一系…

最新AI写作创作系统源码ChatGPT源码,支持AI绘画/支持OpenAI-GPT全模型+国内AI全模型

一、AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统AI绘画系统&#xff0c;支持OpenAI GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署…

池州市的城市环境融合:OLED透明拼接屏展现自然与现代的完美结合

池州是中国安徽省的一个地级市&#xff0c;位于该省的西南部。池州市辖区包括贵池区、东至县、石台县、青阳县等地。 池州市拥有悠久的历史和丰富的文化遗产&#xff0c;同时也以其独特的自然风光而闻名。 首先&#xff0c;让我们来了解一下池州的历史和景点。 池州的历史可…

【TS】笔记-TypeScript环境搭建

TypeScript与JavaScript比较 JavaScript是轻量级的解释性脚本语言&#xff0c;可嵌入到HTML页面中&#xff0c;在浏览器端执行。而TypeScript是JavaScript的超集&#xff0c;即包含JavaScript的所有元素&#xff0c;能运行javaScript的代码&#xff0c;并扩展了JavaScript的语…

竞赛选题 深度学习+opencv+python实现昆虫识别 -图像识别 昆虫识别

文章目录 0 前言1 课题背景2 具体实现3 数据收集和处理3 卷积神经网络2.1卷积层2.2 池化层2.3 激活函数&#xff1a;2.4 全连接层2.5 使用tensorflow中keras模块实现卷积神经网络 4 MobileNetV2网络5 损失函数softmax 交叉熵5.1 softmax函数5.2 交叉熵损失函数 6 优化器SGD7 学…

利达卓越:金融领域的变革者——利达卓越引领行业发展

在过去的几十年里,金融行业面临着经济不确定性、监管压力和竞争加剧等诸多挑战。与此同时,金融领域的迅速发展为行业带来了新的机遇。利达卓越精准地把握了这一机遇,利用先进的科技手段应对挑战。成为金融领域的变革者,引领着金融行业的发展。 利达卓越成立于2015年,至今已有8年…

【Docker】Harbor私有仓库与管理

搭建本地私有仓库 #首先下载 registry 镜像 docker pull registry#在 daemon.json 文件中添加私有镜像仓库地址 vim /etc/docker/daemon.json {"insecure-registries": ["192.168.220.101:5000"], #添加&#xff0c;注意用逗号结尾"registry-mi…

IDEA中创建Web工程流程

第一步&#xff1a;File--》New--》Project 第二步&#xff1a;填写信息&#xff0c;点击Create 第三步&#xff1a;点击File,点击Project Structure 出现该界面 选择相应的版本&#xff0c;这里我用jdk17&#xff0c;点击apply &#xff0c;点击ok 第三步&#xff1a;右键文件…

【C++】异常处理之throw、catch、try、局部资源管理、标准异常库

一、抛出异常 异常处理机制两个主要成分&#xff1a; 异常的鉴定与发出&#xff1b;异常的处理方式。 C通过throw表达式产生异常&#xff1a; inline void Triangular_iterator:: check_integrity() {if(_index>Triangular::max_elems){throw iterator_overflow(_index,…

打造炫酷效果:用Java优雅地制作Excel迷你图

摘要&#xff1a;本文由葡萄城技术团队原创并首发。转载请注明出处&#xff1a;葡萄城官网&#xff0c;葡萄城为开发者提供专业的开发工具、解决方案和服务&#xff0c;赋能开发者。 前言 迷你图是一种简洁而有效的数据可视化方式&#xff0c;常用于展示趋势和变化。它通常由一…

找单身狗2

一个数组中只有两个数字是出现一次&#xff0c;其他所有数字都出现了两次。编写一个函数找出这两个只出现一次的数字。 例如&#xff1a;有数组的元素是&#xff1a;1,2,3,4,5,1,2,3,4,6&#xff0c;只有5和6只出现1次&#xff0c;要找出5和6。 这里我们不妨回忆一下之前找单身…

VS2022新建项目时没有ASP.NET Web应用程序 (.NET Framework)

问题&#xff1a;如图&#xff0c;VS2022新建项目时没有“ASP.NET Web应用程序 &#xff08;.NET Framework&#xff09;”的选项解决方法&#xff1a;点击跳转至修改安装选项界面选择安装该项即可&#xff1a;

金x软件有限公司安全测试岗位面试

目录 一、自我介绍 二、你是网络空间安全专业的&#xff0c;那你介绍下网络空间安全这块主要学习的东西&#xff1f; 三、本科专业是网络工程&#xff0c;在嘉兴海视嘉安智城科技有限公司实习过&#xff0c;你能说下干的工作吗&#xff1f;&#xff08;没想到问的是本科实习…

webdriver.Chrome()没反应

今天学习爬虫安装selenium之后刚开始webdriver.Chrome()正常 后面运行突然卡在这一步了 百度发现是版本不匹配 我们下载旧版本的chrome Download Google Chrome 95.0.4638.69 for Windows - Filehippo.com 禁用chrome的自动更新 打开文件所在位置 点击Google文件夹 右键up…

python:从Excel或者CSV中读取因变量与多个自变量,用于训练机器学习回归模型,并输出预测结果

作者:CSDN @ _养乐多_ 本文详细记录了从Excel读取用于训练机器学习模型的数据,包括独立变量和因变量数据,以供用于机器学习模型的训练。这些机器学习模型包括但不限于随机森林回归模型(RF)和支持向量机回归模型(SVM)。随后,我们将测试数据集应用于这些模型,进行预测和…

PyTorch深度学习实战(20)——从零开始实现Fast R-CNN目标检测

PyTorch深度学习实战&#xff08;20&#xff09;——从零开始实现Fast R-CNN目标检测 0. 前言1. Fast R-CNN1.1 模型架构1.2 R-CNN 与 Fast R-CNN 对比 2. 实现 Fast R-CNN 目标检测2.1 数据处理2.2 模型构建2.4 模型训练与测试 小结系列链接 0. 前言 R-CNN 的主要缺点之一是生…

研发必会-异步编程利器之CompletableFuture(上)

微信公众号访问地址&#xff1a; 近期热推文章&#xff1a; 1、springBoot对接kafka,批量、并发、异步获取消息,并动态、批量插入库表; 2、SpringBoot用线程池ThreadPoolTaskExecutor异步处理百万级数据; 3、基于Redis的Geo实现附近商铺搜索(含源码) 4、基于Redis实现关注、取…

应用在SMPS中的GaN/氮化镓

开关模式电源&#xff08;Switch Mode Power Supply&#xff0c;简称SMPS&#xff09;&#xff0c;又称交换式电源、开关变换器&#xff0c;是一种高频化电能转换装置&#xff0c;是电源供应器的一种。其功能是将一个位准的电压&#xff0c;透过不同形式的架构转换为用户端所需…

文心一言Plugin实战来了,测试开发旅游攻略助手

刚刚过去的8月&#xff0c;百度WAVE SUMMIT 深度学习开发者大会上&#xff0c;重磅发布文心一言的五个原生插件&#xff1a;百度搜索、览卷文档&#xff08;基于文档的交互&#xff09;、E 言易图&#xff08;数据洞察图表生成&#xff09;、说图解画&#xff08;基于图片的交互…

基于Redis+Cookie实现Session共享

分布式项目中要实现单点登录&#xff08;SSO - Single Sign On&#xff09;&#xff1a;对于同一个客户端&#xff08;例如 Chrome 浏览器&#xff09;&#xff0c;只要登录了一个子站&#xff08;例如 a.com&#xff09;&#xff0c;则所有子站&#xff08;b.com、c.com&#…