面试官:Java 线程有哪几种状态?它们之间是怎么切换的?

这篇具有很好参考价值的文章主要介绍了面试官:Java 线程有哪几种状态?它们之间是怎么切换的?。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

来源:https://blog.csdn.net/limenghua9112/article/details/106975105

为何要了解Java线程状态

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

Java线程状态转换图

面试官:Java 线程有哪几种状态?它们之间是怎么切换的?

Java线程有哪些状态?

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

推荐一个开源免费的 Spring Boot 实战项目:

https://github.com/javastacks/spring-boot-best-practice

Thread.State枚举源码:

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

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

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

NEW,TERMINATED

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

RUNNABLE

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

/**
 * 程序目的:观察线程的各种状态
 * created at 2020-06-26 19:09
 * @author lerry
 */
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状态
 * created at 2020-06-26 19:09
 * @author lerry
 */
@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线程执行对应方法。

推荐一个开源免费的 Spring Boot 实战项目:

https://github.com/javastacks/spring-boot-best-practice

WAITING,TIMED_WAITING

如图右侧所示,运行中的线程还会进入等待状态,这两个等待一个是有超时时间的等待,例如调用 Object.waitThread.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.

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

import lombok.extern.slf4j.Slf4j;

/**
 * <pre>
 * 程序目的:观察线程的WAITING状态
 * 模拟:只有一个售票窗口的售票厅,有两个粉丝都想买票。
 * 如果没有票,他们就继续等待、如果有票,则买票、然后离开售票厅。
 * 其中,工作人员会补票,补票之后,粉丝就可以买到票了。
 * </pre>
 * created at 2020-06-26 19:09
 * @author lerry
 */
@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 状态的区别和联系

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

面试官:Java 线程有哪几种状态?它们之间是怎么切换的?

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

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

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

面试官:Java 线程有哪几种状态?它们之间是怎么切换的?

近期热文推荐:

1.1,000+ 道 Java面试题及答案整理(2022最新版)

2.劲爆!Java 协程要来了。。。

3.Spring Boot 2.x 教程,太全了!

4.别再写满屏的爆爆爆炸类了,试试装饰器模式,这才是优雅的方式!!

5.《Java开发手册(嵩山版)》最新发布,速速下载!

觉得不错,别忘了随手点赞+转发哦!文章来源地址https://www.toymoban.com/news/detail-654809.html

到了这里,关于面试官:Java 线程有哪几种状态?它们之间是怎么切换的?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java list 根据id获取对象 有哪几种方式

    在 Java 中,有以下几种方法来根据 ID 获取列表中的对象: 循环遍历列表:遍历整个列表,比较每个对象的 ID 和目标 ID,如果匹配,就返回该对象。 使用 Stream API:使用 Java 8 的 Stream API 操作列表,并使用 filter() 方法筛选出具有指定 ID 的对象。 使用 Map:将对象存储在 Map 中

    2024年02月11日
    浏览(48)
  • SpringBoot面试题8:运行 Spring Boot 有哪几种方式?Spring Boot 需要独立的容器运行吗?

    该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 运行Spring Boot应用有多种方式,具体取决于你的需求和环境。以下是几种常见的运行Spring Boot应用的方式: 使用IDE运行:可以在常用的Java开发环境(如

    2024年02月06日
    浏览(50)
  • 电子商务有哪几种模式

    电子商务通常是指在全球各地广泛的商业贸易活动中,在因特网开放的网络环境下,基于客户端/服务端应用方式,买卖双方不谋面地进行各种商贸活动,实现消费者的网上购物、商户之间的网上交易和在线电子支付以及各种商务活动、交易活动、金融活动和相关的综合服务活

    2024年02月13日
    浏览(55)
  • 运行 Spring Boot 有哪几种方式?

    目录 一、打包用命令或者放到容器中运行 二、用 Maven 插件运行 三、用  Gradle 插件运行 四、直接执行 main 方法运行   通过打包和放到容器中运行SpringBoot项目有以下几种方式: 打包为Jar文件: 使用Maven或Gradle等构建工具,执行打包命令将SpringBoot项目打包为可执行的Jar文件。

    2024年02月13日
    浏览(47)
  • 机器视觉工程师,有哪几种类型

    1.光学实验室(打光机器视觉工程师,一般此职位,要求有光学学历的背景最佳) 2.机器视觉算法开发工程师(此职位国内稀缺)3.机器视觉工程师/机器视觉开发工程师(MV工程师/MV工程师)(国内岗位需求量最多的) 3.机器视觉工程师/机器视觉开发工程师(MV工程师/MV工程师

    2024年02月10日
    浏览(53)
  • MySQL基础扎实——MySQL中有哪几种锁

    在MySQL中,常见的锁包括以下几种: 表级锁(Table-level Lock) :表级锁是对整张表进行锁定,可以分为两种类型: 共享锁(Shared Lock) :也称为读锁(Read Lock),多个事务可以同时持有共享锁,且不互斥。共享锁适用于读操作,不阻塞其他事务的读操作。 排他锁(Exclusive L

    2024年02月15日
    浏览(50)
  • 网页爬虫中常用代理IP主要有哪几种?

    各位爬虫探索者,你是否有想过在网页爬虫中使用代理IP来规避限制实现数据自由?在这篇文章中,作为一名IP代理产品供应商,我将为你揭示常见的网页爬虫代理IP类型,让你在爬虫的世界中游刃有余! 一、免费公开代理IP: 这是最常见的一种代理IP类型。它们通常是一些公

    2024年02月13日
    浏览(55)
  • 有哪几种行为会导致服务器被入侵

    导致服务器被入侵的行为有很多种,以下是一些常见的行为: 系统漏洞:服务器操作系统或软件存在漏洞,攻击者可以通过利用这些漏洞获取系统权限,从而入侵服务器。 弱口令:服务器的账号密码过于简单或者未及时更新,攻击者可以通过暴力破解等手段获取系统权限,

    2024年02月22日
    浏览(51)
  • 边缘计算技术主要有哪几种?如何应用在实际场景中?

    边缘计算是一种新的计算架构,它将计算资源移动到靠近终端用户的边缘设备中,以实现更快、更可靠、更安全的数据传输和处理。边缘AI智能则是指将人工智能算法和模型部署到边缘设备上,使其能够在设备本身上执行计算和决策,而不需要发送数据到云端进行处理。这种

    2024年02月11日
    浏览(46)
  • vue如何解决跨域的问题,有哪几种方式?

    在使用axios发送请求之后,没有得到返回的数据,打开控制台发现如下图所示的报错: 这是提示跨域了。所有使用JavaScript的浏览器都会支持同源策略。同源策略即指域名/协议/端口号相同。只要有一个不同,就会当作跨域请求。  解决方法为: 1. CORS 后端通过 CORS 解决跨域

    2024年02月08日
    浏览(70)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包