既然阻塞 I/O 会使线程休眠,为什么 Java 线程状态却是 RUNNABLE?

简介: 使用 Java 阻塞 I/O 模型读取数据,将会导致线程阻塞,线程将会进入休眠,从而让出 CPU 的执行权,直到数据读取完成。这个期间如果使用 jstack 查看线程状态,却可以发现Java 线程状态是处于 RUNNABLE,这就和上面说的存在矛盾,为什么会这样?上面的矛盾其实是混淆了操作系统线程状态与 Java 线程状态。这里说的线程阻塞进入休眠状态,其实是操作系统层面线程实际状态。而我们使用 jstack 查看的线程状态却是 JVM 中的线程状态。线程是操作系统中一种概念,Java 对其进行了封装,Java 线程本质上就是操作系统的中线程,其状态与操作系统的状态大致相同,但还是存在

使用 Java 阻塞 I/O 模型读取数据,将会导致线程阻塞,线程将会进入休眠,从而让出 CPU 的执行权,直到数据读取完成。这个期间如果使用 jstack 查看线程状态,却可以发现Java 线程状态是处于 RUNNABLE,这就和上面说的存在矛盾,为什么会这样?

上面的矛盾其实是混淆了操作系统线程状态与 Java 线程状态。这里说的线程阻塞进入休眠状态,其实是操作系统层面线程实际状态。而我们使用 jstack 查看的线程状态却是 JVM 中的线程状态。

线程是操作系统中一种概念,Java 对其进行了封装,Java 线程本质上就是操作系统的中线程,其状态与操作系统的状态大致相同,但还是存在一些区别。

下面首先来看我们熟悉的 Java 线程状态。

Java 线程状态

Java 线程状态定义在 Thread.State 枚举中,使用  thread#getState 方法可以获取当前线程的状态。

Thread.State 状态如下图

39.jpg

State.png

可以看到 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

  1. Object#wait

线程在获取到 synchronized 隐式锁后,显示的调用 Object#wait()方法。这种情况下该线程将会让出隐式锁,一旦其他线程获取到该锁,且调用了  Object.notify()object.notifyAll(),线程将会唤醒,然后变成 RUNNABLE

  1. Thread#join

join方法是一种线程同步方法。假设我们在 main 方法中执行 Thread A.join() 方法,main 线程状态就会变成 WATTING。直到 A 线程执行完毕,main 线程才会再变成 RUNNABLE

  1. LockSupport#park()

LockSupport 是 JDK 并发包里重要对象,很多锁的实现都依靠该对象。一旦调用 LockSupport#park(),线程就将会变为 WATTING 状态。如果需要唤醒线程就需要调用 LockSupport#unpark,然后线程状态重新变为 RUNNABLE

RUNNABLE(运行状态) 与 TIMED_WAITING(限时等待状态)

TIMED_WAITINGWATTING 功能一样,只不过前者增加限时等待的功能,一旦等待时间超时,线程状态自动变为 RUNNABLE。以下几种情况将会触发这种状态:

  1. Thread#sleep(long millis)
  2. 占有 synchronized 隐式锁的线程调用 Object.wait (long timeout) 方法
  3. Thread#join (long millis)
  4. LockSupport#parkNanos (Object blocker, long deadline)
  5. LockSupport#parkUntil (long deadline)

RUNNABLE(运行状态)与 TERMINATED(终止状态)

线程一旦执行结束或者线程执行过程发生异常且未正常捕获处理,状态都将会自动变成 TERMINATED

Java 线程 6 种状态看起来挺复杂的,但其实上面 BLOCKEDWATTINGTIMED_WAITING,都会使线程处于休眠状态,所以我们将这三类都归类为休眠状态。这么分类的话,Java 线程生命周期就可以简化为下图:

40.jpg

java线程状态2.png

通用操作系统线程状态

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

41.jpg

操作系统线程状态1.png

这 5 中状态详细情况如下:

  1. 初始状态,这时候线程刚被创建,还不能分配 CPU 。
  2. 可运行状态,线程等待系统分配 CPU ,从而执行任务。
  3. 运行状态,操作系统将 CPU 分配给线程,线程执行任务。
  4. 休眠状态,运行状态下的线程如果调用阻塞 API,如阻塞方式读取文件, 线程状态就将变成休眠状态。这种情况下,线程将会让出 CPU 使用权。休眠结束,线程状态将会先变成可运行状态。
  5. 线程执行结束或者执行过程发生异常将会使线程进入终止状态,这个状态下线程使命已经结束。

对比两者线程状态

比较 Java 线程与操作系统线程,可以发现 Java 线程状态没有可运行状态。也就是说 Java 线程 RUNNABLE 状态包括了操作系统的可运行状态与运行状态。一个处于  RUNNABLE 状态 Java 线程,在操作系统层面状态可能为可运行状态,正在等待系统分配 CPU 使用权。

另外 Java 线程细分了操作系统休眠状态,分成了 BLOCKEDWATTINGTIMED_WAITING 三种。

当线程调用阻塞式 API,线程进入休眠状态,这里指的是操作系统层面的。从 JVM 层面,Java 线程状态依然处于 RUNNABLE 状态。JVM 并不关心操作系统线程实际状态。从 JVM 看来等待 CPU 使用权(操作系统线程状态为可运行状态)与等待 I/O (操作系统线程状态处于休眠状态)没有区别,都是在等待某种资源,所以都归入 RUNNABLE 状态。

其他 Java 线程状态与操作线程状态类似。

欢迎加入我们的知识星球,一起成长,交流经验。加入方式,长按下方二维码噢

最后,我想重复一句话:选择和一群优秀的人一起成长,你成长的速度绝对会不一样!


相关文章
|
3月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
233 1
|
3月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
246 1
|
4月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
Java 数据库 Spring
200 0
|
4月前
|
算法 Java
Java多线程编程:实现线程间数据共享机制
以上就是Java中几种主要处理多线程序列化资源以及协调各自独立运行但需相互配合以完成任务threads 的技术手段与策略。正确应用上述技术将大大增强你程序稳定性与效率同时也降低bug出现率因此深刻理解每项技术背后理论至关重要.
361 16
|
5月前
|
缓存 并行计算 安全
关于Java多线程详解
本文深入讲解Java多线程编程,涵盖基础概念、线程创建与管理、同步机制、并发工具类、线程池、线程安全集合、实战案例及常见问题解决方案,助你掌握高性能并发编程技巧,应对多线程开发中的挑战。
|
5月前
|
数据采集 存储 前端开发
Java爬虫性能优化:多线程抓取JSP动态数据实践
Java爬虫性能优化:多线程抓取JSP动态数据实践
|
3月前
|
Java 调度 数据库
Python threading模块:多线程编程的实战指南
本文深入讲解Python多线程编程,涵盖threading模块的核心用法:线程创建、生命周期、同步机制(锁、信号量、条件变量)、线程通信(队列)、守护线程与线程池应用。结合实战案例,如多线程下载器,帮助开发者提升程序并发性能,适用于I/O密集型任务处理。
380 0
|
3月前
|
Java
如何在Java中进行多线程编程
Java多线程编程常用方式包括:继承Thread类、实现Runnable接口、Callable接口(可返回结果)及使用线程池。推荐线程池以提升性能,避免频繁创建线程。结合同步与通信机制,可有效管理并发任务。
195 6
|
6月前
|
Java API 微服务
为什么虚拟线程将改变Java并发编程?
为什么虚拟线程将改变Java并发编程?
345 83