java 线程状态_面试官问:为什么Java线程没有Running状态?我懵了

点击上方“占小狼的博客”,选择“设为星标“

本文阅读时间大约4分钟。

来源:https://dwz.cn/dLRLBZab

Java虚拟机层面所暴露给我们的状态,与操作系统底层的线程状态是两个不同层面的事。具体而言,这里说的 Java 线程状态均来自于 Thread 类下的 State 这一内部枚举类中所定义的状态:

5d80088987dd14fc4bed357006fee1a6.png

什么是 RUNNABLE?

直接看它的 Javadoc 中的说明:

一个在 JVM 中执行的线程处于这一状态中。(A thread executing in the Java virtual machine is in this state.)

而传统的进(线)程状态一般划分如下:

e24ef7f24ed19253feb048da3d116eca.png

注:这里的进程指早期的单线程进程,这里所谓进程状态实质就是线程状态。

那么 runnable 与图中的 ready 与 running 区别在哪呢?

与传统的ready状态的区别

更具体点,javadoc 中是这样说的:

处于 runnable 状态下的线程正在 Java 虚拟机中执行,但它可能正在等待来自于操作系统的其它资源,比如处理器。

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 状态实质上是包括了 ready 状态的。

甚至还可能有包括上图中的 waiting 状态的部分细分状态,在后面我们将会看到这一点。

与传统的running状态的区别

有人常觉得 Java 线程状态中还少了个 running 状态,这其实是把两个不同层面的状态混淆了。对 Java 线程状态而言,不存在所谓的running 状态,它的 runnable 状态包含了 running 状态。

我们可能会问,为何 JVM 中没有去区分这两种状态呢?

现在的时分(time-sharing)多任务(multi-task)操作系统架构通常都是用所谓的“时间分片(time quantum or time slice)”方式进行抢占式(preemptive)轮转调度(round-robin式)。

更复杂的可能还会加入优先级(priority)的机制。

这个时间分片通常是很小的,一个线程一次最多只能在 cpu 上运行比如10-20ms 的时间(此时处于 running 状态),也即大概只有0.01秒这一量级,时间片用后就要被切换下来放入调度队列的末尾等待再次调度。(也即回到 ready 状态)

注:如果期间进行了 I/O 的操作还会导致提前释放时间分片,并进入等待队列。

又或者是时间分片没有用完就被抢占,这时也是回到 ready 状态。

这一切换的过程称为线程的上下文切换(context switch),当然 cpu 不是简单地把线程踢开就完了,还需要把被相应的执行状态保存到内存中以便后续的恢复执行。

显然,10-20ms 对人而言是很快的,

不计切换开销(每次在1ms 以内),相当于1秒内有50-100次切换。事实上时间片经常没用完,线程就因为各种原因被中断,实际发生的切换次数还会更多。

也这正是单核 *CPU 上实现所谓的“并发*(concurrent)”的基本原理,但其实是快速切换所带来的假象,这有点类似一个手脚非常快的杂耍演员可以让好多个球同时在空中运转那般。

时间分片也是可配置的,如果不追求在多个线程间很快的响应,也可以把这个时间配置得大一点,以减少切换带来的开销。

如果是多核CPU,才有可能实现真正意义上的并发,这种情况通常也叫并行(pararell),不过你可能也会看到这两词会被混着用,这里就不去纠结它们的区别了。

通常,Java的线程状态是服务于监控的,如果线程切换得是如此之快,那么区分 ready 与 running 就没什么太大意义了。

当你看到监控上显示是 running 时,对应的线程可能早就被切换下去了,甚至又再次地切换了上来,也许你只能看到 ready 与 running 两个状态在快速地闪烁。

当然,对于精确的性能评估而言,获得准确的 running 时间是有必要的。

现今主流的 JVM 实现都把 Java 线程一一映射到操作系统底层的线程上,把调度委托给了操作系统,我们在虚拟机层面看到的状态实质是对底层状态的映射及包装。JVM 本身没有做什么实质的调度,把底层的 ready 及 running 状态映射上来也没多大意义,因此,统一成为runnable 状态是不错的选择。

我们将看到,Java 线程状态的改变通常只与自身显式引入的机制有关。

当I/O阻塞时

我们知道传统的I/O都是阻塞式(blocked)的,原因是I/O操作比起cpu来实在是太慢了,可能差到好几个数量级都说不定。如果让 cpu 去等I/O 的操作,很可能时间片都用完了,I/O 操作还没完成呢,不管怎样,它会导致 cpu 的利用率极低。

所以,解决办法就是:一旦线程中执行到 I/O 有关的代码,相应线程立马被切走,然后调度 ready 队列中另一个线程来运行。

这时执行了 I/O 的线程就不再运行,即所谓的被阻塞了。它也不会被放到调度队列中去,因为很可能再次调度到它时,I/O 可能仍没有完成。

线程会被放到所谓的等待队列中,处于上图中的 waiting 状态:

08a0509384c37f171cc1c03c49b48e55.png

当然了,我们所谓阻塞只是指这段时间 cpu 暂时不会理它了,但另一个部件比如硬盘则在努力地为它服务。cpu 与硬盘间是并发的。如果把线程视作为一个 job,这一 job 由 cpu 与硬盘交替协作完成,当在 cpu 上是 waiting 时,在硬盘上却处于 running,只是我们在操作系统层面讨论线程状态时通常是围绕着 cpu 这一中心去述说的。

而当 I/O 完成时,则用一种叫中断(interrupt)的机制来通知 cpu:

也即所谓的“中断驱动(interrupt-driven)”,现代操作系统基本都采用这一机制。

某种意义上,这也是控制反转(IoC)机制的一种体现,cpu不用反复去询问硬盘,这也是所谓的“好莱坞原则”—Don’t call us, we will call you.好莱坞的经纪人经常对演员们说:“别打电话给我,(有戏时)我们会打电话给你。”

在这里,硬盘与 cpu 的互动机制也是类似,硬盘对 cpu 说:”别老来问我 IO 做完了没有,完了我自然会通知你的“

当然了,cpu 还是要不断地检查中断,就好比演员们也要时刻注意接听电话,不过这总好过不断主动去询问,毕竟绝大多数的询问都将是徒劳的。

cpu 会收到一个比如说来自硬盘的中断信号,并进入中断处理例程,手头正在执行的线程因此被打断,回到 ready 队列。而先前因 I/O 而waiting 的线程随着 I/O 的完成也再次回到 ready 队列,这时 cpu 可能会选择它来执行。

另一方面,所谓的时间分片轮转本质上也是由一个定时器定时中断来驱动的,可以使线程从 running 回到 ready 状态:

a3835e21802f67ff2cd61d6f21e06d58.png

比如设置一个10ms 的倒计时,时间一到就发一个中断,好像大限已到一样,然后重置倒计时,如此循环。

与 cpu 正打得火热的线程可能不情愿听到这一中断信号,因为它意味着这一次与 cpu 缠绵的时间又要到头了......奴为出来难,何日君再来?

现在我们再看一下 Java 中定义的线程状态,嘿,它也有 BLOCKED(阻塞),也有 WAITING(等待),甚至它还更细,还有TIMED_WAITING:

07d470180e49453f6af86899b5ffdbb2.png

现在问题来了,进行阻塞式 I/O 操作时,Java 的线程状态究竟是什么?是 BLOCKED?还是 WAITING?

可能你已经猜到,既然放到 RUNNABLE 这一主题下讨论,其实状态还是 RUNNABLE。我们也可以通过一些测试来验证这一点:

@Test

public void testInBlockedIOState() throws InterruptedException {

Scanner in = new Scanner(System.in);

// 创建一个名为“输入输出”的线程t

Thread t = new Thread(new Runnable() {

@Override

public void run() {

try {

// 命令行中的阻塞读

String input = in.nextLine();

System.out.println(input);

} catch (Exception e) {

e.printStackTrace();

} finally {

IOUtils.closeQuietly(in);

}

}

}, "输入输出"); // 线程的名字

// 启动

t.start();

// 确保run已经得到执行

Thread.sleep(100);

// 状态为RUNNABLE

assertThat(t.getState()).isEqualTo(Thread.State.RUNNABLE);

}

在最后的语句上加一断点,监控上也反映了这一点:

45c3bab5577b70bc4a0f21b7855b4de9.png

网络阻塞时同理,比如socket.accept,我们说这是一个“阻塞式(blocked)”式方法,但线程状态还是 RUNNABLE。

@Test

public void testBlockedSocketState() throws Exception {

Thread serverThread = new Thread(new Runnable() {

@Override

public void run() {

ServerSocket serverSocket = null;

try {

serverSocket = new ServerSocket(10086);

while (true) {

// 阻塞的accept方法

Socket socket = serverSocket.accept();

// TODO

}

} catch (IOException e) {

e.printStackTrace();

} finally {

try {

serverSocket.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

}, "socket线程"); // 线程的名字

serverThread.start();

// 确保run已经得到执行

Thread.sleep(500);

// 状态为RUNNABLE

assertThat(serverThread.getState()).isEqualTo(Thread.State.RUNNABLE);

}

监控显示:

2feec1ff26f4916bde1b7b1db23fd23c.png

当然,Java 很早就引入了所谓 nio(新的IO)包,至于用 nio 时线程状态究竟是怎样的,这里就不再一一具体去分析了。

至少我们看到了,进行传统上的 IO 操作时,口语上我们也会说“阻塞”,但这个“阻塞”与线程的 BLOCKED 状态是两码事!

如何看待RUNNABLE状态?

首先还是前面说的,注意分清两个层面:

61f50be22e85f6a809f24a21a4894997.png

虚拟机是骑在你操作系统上面的,身下的操作系统是作为某种资源为满足虚拟机的需求而存在的:

4928b2b146e350ba333da404e5681e35.png

当进行阻塞式的 IO 操作时,或许底层的操作系统线程确实处在阻塞状态,但我们关心的是 JVM 的线程状态。

JVM 并不关心底层的实现细节,什么时间分片也好,什么 IO 时就要切换也好,它并不关心。

前面说到,“处于 runnable 状态下的线程正在* Java 虚拟机中执行,但它可能正在等待*来自于操作系统的其它资源,比如处理器。”

JVM 把那些都视作资源,cpu 也好,硬盘,网卡也罢,有东西在为线程服务,它就认为线程在“执行”。

你用嘴,用手,还是用什么鸟东西来满足它的需求,它并不关心~

处于 IO 阻塞,只是说 cpu 不执行线程了,但网卡可能还在监听呀,虽然可能暂时没有收到数据:

就好比前台或保安坐在他们的位置上,可能没有接待什么人,但你能说他们没在工作吗?

所以 JVM 认为线程还在执行。而操作系统的线程状态是围绕着 cpu 这一核心去述说的,这与 JVM 的侧重点是有所不同的。

前面我们也强调了“Java 线程状态的改变通常只与自身显式引入的机制有关”,如果 JVM 中的线程状态发生改变了,通常是自身机制引发的。

比如 synchronize 机制有可能让线程进入BLOCKED 状态,sleep,wait等方法则可能让其进入 WATING 之类的状态。

它与传统的线程状态的对应可以如下来看:

4c5f75a8f0ace4c3b576ef0c20b725b5.png

RUNNABLE 状态对应了传统的 ready, running 以及部分的 waiting 状态。

近期热文

 
  • 整理了一些 IDEA 中比较骚的技巧

  • 7年Java后端被淘汰,一路北漂辛酸史

  • Java8 的这个特性,用起来真的很爽!

d7a77cdf7a45e263cc386d6ce6c70ab7.png

最后,分享一份面试宝典《Java核心知识点整理.pdf》,覆盖了JVM、锁、高并发、反射、Spring原理、微服务、Zookeeper、数据库、数据结构等等。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

明天见(。・ω・。)ノ♡

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

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

相关文章

viewpager 自定义翻页效果_Android RecyclerView自定义LayoutManager

在第一篇中已经讲过,LayoutManager主要用于布局其中的Item,在LayoutManager中能够对每个Item的大小,位置进行更改,将它放在我们想要的位置,在很多优秀的效果中,都是通过自定义LayoutManager来实现的&#x…

delphi gui编辑工具源码_Python 快速构建一个简单的 GUI 应用

点击上方“AirPython”,选择“加为星标”第一时间关注 Python 技术干货!1. 介绍Python GUI 常用的 3 种框架是:Tkinter、wxpython、PyQt5PyQt5 基于 Qt,是 Python 和 Qt 的结合体,可以用 Python 语言编写跨平台的 GUI …

Python入门级教学之(Python中的输出函数)

print()函数 括号内容可以是数字、字符串、含有运算符的表达式 输出的目的地是显示器、文件 输出的形式是换行、不换行 代码演示: # 项目负责人: LBJ # 开发日期:2021/3/16 20:36# 输出数字、字符串、运算表达式 print(123) print("123") pri…

processing创意图形代码_2020年外贸B2C店铺的黑色星期五创意营销想法(下)

10.外贸B2C店铺黑色星期五创意营销理念——创建促销内容日历随着黑色星期五的临近,您将希望巩固自己的整体策略。伟大的第一步是创建一个内容日历,其中要共享什么资产和内容以及何时共享。计划提前一个月计划,并在黑色星期五的一周开始促销活…

python 识别图形验证码_Python验证码识别

大致介绍在python爬虫爬取某些网站的验证码的时候可能会遇到验证码识别的问题,现在的验证码大多分为四类:1、计算验证码2、滑块验证码3、识图验证码4、语音验证码这篇博客主要写的就是识图验证码,识别的是简单的验证码,要想让识别…

pep8 python 编码规范_实用的python编码规范

编码规范在程序开发中是一项很重要要求,良好的编码规范对程序的可读性、代码的可维护性都有很大的提高,从而提高开发效率。下面总结了python中一些实用的开发规范,供大家借鉴和参考。1.每行不超过80个字符每行代码太长既不美观也影响可读性&a…

python txt转json_实战篇 | 用Python来找你喜欢的妹子(二)

用Python做有趣的事情最近整理一个爬虫系列方面的文章,不管大家的基础如何,我从头开始整一个爬虫系列方面的文章,让大家循序渐进的学习爬虫,小白也没有学习障碍.爬虫篇:使用Python动态爬取某大V微博,再用词…

linux 切换root账号_Linux 服务器的安全保障,看看这些

作为一种开放源代码的操作系统,Linux 服务器以其安全,高效和稳定的显著优势而得以广泛应用,但是,若不加以控制,也不见得安全到哪里,这篇博文主要从账号安全控制、系统引导和登录控制的角度,来进…

JAVA进阶开发之(异常类)

保姆级别的异常类教学(附代码) 目录 1、什么是异常: 2、 java语言中异常以什么形式存在的呢? 3、异常对应的现实生活中是怎样的? 4、异常处理机制 5、异常处理的具体方式 6、运行时异常编写程序时可以不处理 7…

bat文件打开一闪就没了_window文件夹目录树的创建

问题描述当我们的项目文件夹逐渐丰满,各类文件种类繁多,为了增强项目资料的自明性,便于资料管理和交接,有必要对项目文件进行汇总说明。要进行文件汇总,首先要提取文件的目录树。什么是目录树呢?在cmd黑窗口…

JAVA进阶开发之(异常练习题)

编写程序模拟用户注册: 1、程序开始执行的时候,提示用户输入“用户名”和“密码”信息 2、输入信息之后,后台JAVA程序模拟用户注册 3、注册时用户名要求长度在【6-14】之间,小于或者大于都表示异常 注意: 完成注册的…

boolean 默认_MySQL数据类型测试:BOOLEAN、TINYINT测试数据总结(第八节)

前面几节Amy给大家分享了STE集合类型和枚举类型ENUM的测试数据,还没来得及看到的读者可以点击这里查看往期文章。之前看到一些平台上很多人咨询mysql是否提供布尔类型的问题。MySQL数据库确实提供布尔类型,此数据类型的关键字可以写成:布尔类…

netty cpu 占用率 高_Netty 是如何支撑高性能网络通信的?

作为一个高性能的 NIO 通信框架,Netty 被广泛应用于大数据处理、互联网消息中间件、游戏和金融行业等。大多数应用场景对底层的通信框架都有很高的性能要求,作为综合性能最高的 NIO 框架 之一,Netty 可以完全满足不同领域对高性能通信的需求。…

JAVA进阶教学之(单链表数据结构)

什么是链表: 内存地址不连续,基本单元是节点对象Node,单链表尾结点默认是null 什么是链表中的节点: 节点是单链表中的基本单元,每一个节点都有两个属性 属性一:存储的数据 属性二:下一个节点的内…

eclipse maven打包_Maven 学习

1 Maven 简介Maven 使用项目对象模型(POM,Project Object Model) 的概念,可以通过一小段描述信息来管理项目的构建、报告和文档的软件项目管理工具2 Maven 的下载与IDE 的整合1 下载地址http://maven.apche.org2 Eclipse 整合Mave…

口琴膜片什么作用_2020年半音阶口琴选购攻略,让小白告别选择困难

为什么我会喜欢上口琴,其实是因为小时候看到《数码宝贝》里的阿和吹奏口琴,当时觉得很酷。不过家里条件很差,想要一个但是没提,后来工作买了一把铃木的。不过选口琴是一个很麻烦的事情,品类太多。在了解的过程中&#…

JAVA进阶教学之(集合)

目录 1、集合概述 2、集合存储的数据类型 3、不同的集合,底层都会对应不同的数据结构 4、集合继承结构图(部分接口和类) 5、Collection接口中常用的方法 6、Collection 集合迭代(遍历) 7、Collection的contains…

python中有数组吗_python有数组吗

广告关闭 腾讯云11.11云上盛惠 ,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高返5000元!感悟: 1.python列表操作里不允许变量类型的指针2.case1类似于冒泡排序操作,这个是满足…

flutter天气_牛笔!自己用Flutter撸一个天气APP

这是一款简约风格的 flutter 天气项目,提供实时、多日、24 小时、台风路径以及生活指数等服务,支持定位、删除、搜索等操作。下图为主页效果:开始本身作为天气 APP,自定义绘制自然少不了,首页多样的背景效果&#xff0…

电脑远程凭证不工作:解决

电脑 远程桌面连接你的凭据不工作解决方法 方法/步骤 第一步我们首先需要知道远程桌面连接你的凭据不工作原因是,远程的电脑拒绝了访问,需要设置在远程的电脑上设置安全选项,按winR键,打开运行,输入“gpedit.msc”&a…