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 状态:
![为什么Java线程没有Running状态?](https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2023%2F0103%2F1f8c6d0fj00rnwxws000fd2008g006ig00it00eh.jpg&thumbnail=660x2147483647&quality=80&type=jpg)
文章插图
当然了,我们所谓阻塞只是指这段时间 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 状态:
![为什么Java线程没有Running状态?](https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2023%2F0103%2F20df4707p00rnwxwt000hd2009n003ug00it007h.png&thumbnail=660x2147483647&quality=80&type=jpg)
文章插图
比如设置一个10ms 的倒计时,时间一到就发一个中断,好像大限已到一样,然后重置倒计时,如此循环 。
与 cpu 正打得火热的线程可能不情愿听到这一中断信号,因为它意味着这一次与 cpu 缠绵的时间又要到头了……奴为出来难,何日君再来?
现在我们再看一下 Java 中定义的线程状态,嘿,它也有 BLOCKED(阻塞),也有 WAITING(等待),甚至它还更细,还有TIMED_WAITING:
![为什么Java线程没有Running状态?](https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2023%2F0103%2Fa6935748p00rnwxwt0001d2004h0040g00it00gt.png&thumbnail=660x2147483647&quality=80&type=jpg)
文章插图
现在问题来了,进行阻塞式 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();
推荐阅读
- 为什么短短两周之后就鲜有人继续讨论ChatGPT了?
- 为什么有些腕表被称为医生表?
- cmpunk出场曲?wwe CMPUNK 的GTS 永远沉睡 怎么用 我为什么每次扔人都是 仰着头下去的而不是面朝下呢?
- 银元|为什么钱币收藏非常适合大众而且可以一直收藏
- 诸葛亮为什么要七擒七纵孟获? 七擒七纵孟获
- 宋祖儿|李连杰又去拜佛,61岁利智和2个女儿陪同,他为什么如此热衷佛学
- 玛莎百货在英国现状?为什么玛莎百货全面退出中国?
- 请问现在58同城,百姓网,赶集网为什么看不到企业招聘的电话啊 呼伦贝尔百姓赶集网
- |中年男人,为什么喜欢钓鱼?
- 变压器容量单位为什么是VA而不是W 变压器容量单位