面试官:都说阻塞 I/O 模型将会使线程休眠,为什么 Java 线程状态却是 RUNNABLE?

摘要: 原创出处 https://studyidea.cn 「公众号:程序通事 」欢迎关注和转载,保留摘要,谢谢!
使用 Java 阻塞 I/O 模型读取数据,将会导致线程阻塞,线程将会进入休眠,从而让出 CPU 的执行权,直到数据读取完成。这个期间如果使用 jstack 查看线程状态,却可以发现Java 线程状态是处于 RUNNABLE,这就和上面说的存在矛盾,为什么会这样?
上面的矛盾其实是混淆了操作系统线程状态与 Java 线程状态。这里说的线程阻塞进入休眠状态,其实是操作系统层面线程实际状态。而我们使用 jstack 查看的线程状态却是 JVM 中的线程状态。
线程是操作系统中一种概念,Java 对其进行了封装,Java 线程本质上就是操作系统的中线程,其状态与操作系统的状态大致相同,但还是存在一些区别。
下面首先来看我们熟悉的 Java 线程状态。
Java 线程状态
Java 线程状态定义在 Thread.State 枚举中,使用 thread#getState 方法可以获取当前线程的状态。
Thread.State 状态如下图:

可以看到 Java 线程总共存在 6 中状态,分别为:
- NEW(初始状态)
- RUNNABLE(运行状态)
- BLOCKED(阻塞状态)
- WATTING(等待状态)
- TIMED_WAITING(限时等待状态)
- TERMINATED(终止状态)
NEW(初始状态)与 RUNNABLE(运行状态)
每个使用 new Thread() 刚创建出线程实例状态处于 NEW 状态,一旦调用 thread.start(),线程状态将会变成 RUNNABLE。
RUNNABLE(运行状态) 与 BLOCKED(阻塞状态)
RUNNABLE 状态的线程在进入由 synchronized修饰的方法或代码块前将会尝试获取一把隐式的排他锁,一旦获取不到,线程状态将会变成 BLOCKED,等待获取锁。一旦有其他线程释放这把锁,线程成功抢到该锁,线程状态就将会从 BLOCKED 转变为 RUNNABLE 状态。
RUNNABLE(运行状态) 与 WATTING(等待状态)
处于 WATTING 状态的线程将会一直处于无限期的等待状态,需要等待其他线程唤醒。总共存在三种方法将会使线程从 RUNNABLE 变成 WATTING。
Object#wait
线程在获取到 synchronized 隐式锁后,显示的调用 Object#wait()方法。这种情况下该线程将会让出隐式锁,一旦其他线程获取到该锁,且调用了 Object.notify() 或object.notifyAll(),线程将会唤醒,然后变成 RUNNABLE。
Thread#join
join方法是一种线程同步方法。假设我们在 main 方法中执行 Thread A.join() 方法,main 线程状态就会变成 WATTING。直到 A 线程执行完毕,main 线程才会再变成 RUNNABLE。
LockSupport#park()
LockSupport 是 JDK 并发包里重要对象,很多锁的实现都依靠该对象。一旦调用 LockSupport#park(),线程就将会变为 WATTING 状态。如果需要唤醒线程就需要调用 LockSupport#unpark,然后线程状态重新变为 RUNNABLE。
RUNNABLE(运行状态) 与 TIMED_WAITING(限时等待状态)
TIMED_WAITING 与 WATTING 功能一样,只不过前者增加限时等待的功能,一旦等待时间超时,线程状态自动变为 RUNNABLE。以下几种情况将会触发这种状态:
Thread#sleep(long millis)- 占有 synchronized 隐式锁的线程调用
Object.wait (long timeout)方法 Thread#join (long millis)LockSupport#parkNanos (Object blocker, long deadline)LockSupport#parkUntil (long deadline)
RUNNABLE(运行状态)与 TERMINATED(终止状态)
线程一旦执行结束或者线程执行过程发生异常且未正常捕获处理,状态都将会自动变成 TERMINATED。
Java 线程 6 种状态看起来挺复杂的,但其实上面 BLOCKED,WATTING,TIMED_WAITING,都会使线程处于休眠状态,所以我们将这三类都归类为休眠状态。这么分类的话,Java 线程生命周期就可以简化为下图:

通用操作系统线程状态
上面讲完 Java 系统的线程状态,我们来看下通用操作系统的线程状态。操作系统线程状态可以分为初始状态,可运行状态,运行状态,休眠状态以及终止状态,如下图:

这 5 中状态详细情况如下:
- 初始状态,这时候线程刚被创建,还不能分配 CPU 。
- 可运行状态,线程等待系统分配 CPU ,从而执行任务。
- 运行状态,操作系统将 CPU 分配给线程,线程执行任务。
- 休眠状态,运行状态下的线程如果调用阻塞 API,如阻塞方式读取文件, 线程状态就将变成休眠状态。这种情况下,线程将会让出 CPU 使用权。休眠结束,线程状态将会先变成可运行状态。
- 线程执行结束或者执行过程发生异常将会使线程进入终止状态,这个状态下线程使命已经结束。
对比两者线程状态
比较 Java 线程与操作系统线程,可以发现 Java 线程状态没有可运行状态。也就是说 Java 线程 RUNNABLE 状态包括了操作系统的可运行状态与运行状态。一个处于 RUNNABLE 状态 Java 线程,在操作系统层面状态可能为可运行状态,正在等待系统分配 CPU 使用权。
另外 Java 线程细分了操作系统休眠状态,分成了 BLOCKED,WATTING,TIMED_WAITING 三种。
当线程调用阻塞式 API,线程进入休眠状态,这里指的是操作系统层面的。从 JVM 层面,Java 线程状态依然处于 RUNNABLE 状态。JVM 并不关心操作系统线程实际状态。从 JVM 看来等待 CPU 使用权(操作系统线程状态为可运行状态)与等待 I/O (操作系统线程状态处于休眠状态)没有区别,都是在等待某种资源,所以都归入 RUNNABLE 状态。
其他 Java 线程状态与操作线程状态类似。
面试官:都说阻塞 I/O 模型将会使线程休眠,为什么 Java 线程状态却是 RUNNABLE?

面试官:都说阻塞 I/O 模型将会使线程休眠,为什么 Java 线程状态却是 RUNNABLE?的更多相关文章
- 面试官都叫好的Synchronized底层实现,这工资开多少一个月?
本文为死磕Synchronized底层实现第三篇文章,内容为重量级锁实现. 本系列文章将对HotSpot的synchronized锁实现进行全面分析,内容包括偏向锁.轻量级锁.重量级锁的加锁.解锁.锁 ...
- 【java线程系列】java线程系列之线程间的交互wait()/notify()/notifyAll()及生产者与消费者模型
关于线程,博主写过java线程详解基本上把java线程的基础知识都讲解到位了,但是那还远远不够,多线程的存在就是为了让多个线程去协作来完成某一具体任务,比如生产者与消费者模型,因此了解线程间的协作是非 ...
- 「mysql优化专题」主从复制面试宝典!面试官都没你懂得多!(11)
内容较多,可先收藏,目录如下: 一.什么是主从复制 二.主从复制的作用(重点) 三.主从复制的原理(重中之重) 四.三步轻松构建主从 五.必问面试题干货分析(最最重要的点) 一.什么是主从复制(技术文 ...
- 面试 HTTP ,99% 的面试官都爱问这些问题
HTTP 和 HTTPS 的区别 HTTP 是一种 超文本传输协议(Hypertext Transfer Protocol),HTTP 是一个在计算机世界里专门在两点之间传输文字.图片.音频.视频等超 ...
- 深度分析:面试腾讯,阿里面试官都喜欢问的String源码,看完你学会了吗?
前言 最近花了两天时间,整理了一下String的源码.这个整理并不全面但是也涵盖了大部分Spring源码中的方法.后续如果有时间还会将剩余的未整理的方法更新到这篇文章中.方便以后的复习和面试使用.如果 ...
- 这些Servlet知识你一定要知道,金九银十大厂面试官都爱问
前言 Servlet是服务器端的Java应用程序,可以生产动态Web页面.透过JSP执行过程可以知道JSP最终被编译成一个.class文件,查看该文件对应的Java类,发现该Java类继承自org.a ...
- 面试官写了个双冒号: : 问我这是什么语法?Java中有这玩意?
一:简洁 方法引用分为三种,方法引用通过一对双冒号:: 来表示,方法引用是一种函数式接口的另一种书写方式 静态方法引用,通过类名::静态方法名, 如 Integer::parseInt 实例方法引用, ...
- java线程基础知识----java线程模型
转载自http://www.cnblogs.com/nexiyi/p/java_memory_model_and_thread.html 1. 概述 多任务和高并发是衡量一台计算机处理器的能力重要指标 ...
- 面试官:JVM锁优化都优化了啥?
从JDK1.6开始,JVM对锁进行了各种优化,目的就是为了在线程间更高效的共享数据和解决互斥同步的问题.从锁优化的话题开始,可以引申出很多考点面试题,比如锁优化的技术.各优化技术的细节.CAS实现原理 ...
随机推荐
- 吃货眼中的sqlalchemy外键和连表查询
前言 使用数据库一个高效的操作是连表查询,一条查询语句能够查询到多个表的数据.在sqlalchem架构下的数据库连表查询更是十分方便.那么如何连表查询?以及数据库外键对连表查询有没有帮助呢?本篇文章就 ...
- 剑指Offer(二十八):数组中出现次数超过一半的数字
剑指Offer(二十八):数组中出现次数超过一半的数字 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn. ...
- CodeForces - 940E - Cashback +贪心+DP
传送门:CodeForces - 940E - Cashback 题意:在一个长度为n的数组中,可以分出长度为 k 连续的多个数组b(每个数组 b 的 k 可不相同),然后,可以对每个数组 b 进行删 ...
- 2019 Multi-University Training Contest 4
A. AND Minimum Spanning Tree solved by rdc 21min -1 数组开小了,解体了一次. 题意 给一棵树,两点之间边权为 x & y,求最小生成树. 做 ...
- 2018年全国多校算法寒假训练营练习比赛(第五场)H Tree Recovery
Tree Recovery 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 131072K,其他语言262144K 64bit IO Format: %lld 链接:https://w ...
- HTML5基本介绍
HTML5简介 HTML是互联网上应用最广泛的标记语言.HTML文件就是普通文本+HTML标记,而不同的HTML标记能表示不同的效果.(简单的说HTML是超文本标记语言) HTML5草案的前身名为 W ...
- 洛谷 P1980【计数问题】 题解(1)
鉴于数据最高只有七位数,通过判断数位,逐位判断即可完成本题. (运行很快,打得手疼) //Stand up for the faith!#include<bits/stdc++.h> us ...
- javascript合并两个数组
在开发的过程中,我们很多时候会遇到需要将两个数组合并成一个数组的情况出现. var arr1 = [1, 2, 3]; var arr2 = [4, 5, 6]; // 将arr1和arr2合并成为[ ...
- Spring系列__04AOP
AOP简介 今天来介绍一下AOP.AOP,中文常被翻译为"面向切面编程",其作为OOP的扩展,其思想除了在Spring中得到了应用,也是不错的设计方法.通常情况下,一个软件系统,除 ...
- Appium+python自动化(三十七)- 士兵突击许三多 - 多个appium服务启动,多个设备启动,多进程并发启动设备-并发测试 - 下(超详解)
简介 接着上一篇继续看一下如何并发测试以及并发测试的过程中,可能遇到的问题,在这里宏哥把宏哥遇到的和小伙伴或者童鞋们,一起分享一下. Appium端口检测 问题思考 经过前面学习,我们已经能够使用py ...