Java线程状态RUNNABLE详解

你不知道的Runnable接口,深度解析Runnable接口 本文描述的是Android中的Runnable接口。因Android中的线程源自于Java,所以首先需要了解Java中的线程,有关Java中的线程请看这篇文章Android(线程一) 线程 ! Java开发中,我们实现多线程,有两种方式,一种是继承Thread类,一种是实现Runnable接口。但是,我们真的理解Runnable?Runnable和Thread一样吗?都是开... 阅读详情

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

在这里插入图片描述

什么是 RUNNABLE?

直接看它的 Javadoc 中的说明:

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

而传统的进(线)程状态一般划分如下(基于操作系统层面,以cpu为中心):

在这里插入图片描述

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

那么 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,才有可能实现真正意义上的并发,这种情况通常也叫并行 ,不过你可能也会看到这两词会被混着用,这里就不去纠结它们的区别了。

通常,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 状态:

在这里插入图片描述
当然了,我们所谓阻塞只是指这段时间 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 状态:

在这里插入图片描述
比如设置一个10ms 的倒计时,时间一到就发一个中断,好像大限已到一样,然后重置倒计时,如此循环。与 cpu 正打得火热的线程可能不情愿听到这一中断信号,因为它意味着这一次与 cpu 缠绵的时间又要到头了…奴为出来难,何日君再来?

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

现在问题来了,进行阻塞式 I/O 操作时(相对于底层系统cpu来说,线程为阻塞状态),Java 的线程状态(jvm层面上)究竟是什么?是 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);
}

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

在这里插入图片描述
网络阻塞时同理,比如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);

}

监控显示:

在这里插入图片描述
当然,Java 很早就引入了所谓 nio(新的IO)包,至于用 nio 时线程状态究竟是怎样的,这里就不再一一具体去分析了。至少我们看到了,进行传统上的 IO 操作时,口语上我们也会说“阻塞”,但这个“阻塞”与线程的 BLOCKED 状态是两码事!

如何看待RUNNABLE状态?

首先还是前面说的,注意分清两个层面:虚拟机层面(vm)和操作系统层面(os)

虚拟机是骑在你操作系统上面的,身下的操作系统是作为某种资源为满足虚拟机的需求而存在的。当进行阻塞式的 IO 操作时,或许底层的操作系统线程确实处在阻塞状态,但我们关心的是 JVM 的线程状态。

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

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

JVM 把那些都视作资源,cpu 也好,硬盘,网卡也罢,有东西在为线程服务,它就认为线程在“执行”。处于 IO 阻塞,只是说 cpu 不执行线程了,但网卡可能还在监听呀,虽然可能暂时没有收到数据。

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

前面我们也强调了“Java 线程状态的改变通常只与自身显式引入的机制有关”,如果 JVM 中的线程状态发生改变了,通常是自身机制引发的。比如 synchronize 机制有可能让线程进入BLOCKED 状态,sleep,wait等方法则可能让其进入 WATING 之类的状态。

【Android -- 多线程】继承 Thread Android 多线程之 Thread 阅读详情

相关推荐

Java线程生命周期详解与状态转换实战

线程是Java并发编程的核心概念,其生命周期管理直接影响系统性能与稳定性。从操作系统层面看,线程本质是CPU调度的基本单位,Java通过Thread.State枚举定义了NEW、RUNNABLE、BLOCKED等6种状态。理解这些状态的转换机制,就像掌握多线程编程的交通规则,能有效避免死锁、资源竞争等并发问题。在实际工程中,通过jstack、jvisualvm等工具监控线程状态分布,可以快速定位性能瓶颈,例如当大量线程处于BLOCKED状态时往往需要优化锁策略,而TIMED_WAITING线程过多可能暗示I

weixin_34289454的博客 332

Java 线程状态之 RUNNABLE

深入探讨了 Java 线程的 RUNNABLE 状态, 特别是对处在 IO 阻塞时的状态进行了深入分析.

Just do IT 5822

Java并发编程系列02:实现Runnable接口方式实现多线程

1、实现Runnable接口方式实现多线程 与继承Thread类相比,实现Runnable接口的MyThread类在结构上与之前是没有区别的,但是有一点是有严重区别的,如果继承了Thread类,那么可以直接继承start()方法,但是实现Runnable接口是没有start()方法的。不管何种情况下,如果要启动多线程一定要依靠Thread类完成。 如果自己的类已经extends另一个类,就无法直接extends Thread,此时,必须实现一个Runnable接口,如下: 1.1定义Runna

fen_fen的专栏 7350

【Java】线程基本使用——实现 Runnable接口

实现 Runnable 接口 说明 java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类方法来创建线程显然不可能了。 java设计者们提供了另外一个方式创建线程,就是通过实现Runnable接口来创建线程 应用案例 请编写程序,该程序可以每隔1秒。在控制台输出“hi!”,当输出10次后,自动退出。 请使用实现Runnable接口的方式实现。这里底层使用了设计模式【代理模式】=> 代码模拟实现Runnable接口 开发线程的机制。 ...

兮动人 2万+

【物理模拟】PBD算法详解

参考MatthiaMuller的十分钟物理(他就是PBD算法的发明者)

beidou111的博客 9499

FPGA远程升级避坑实录:用STARTUPE2原语搞定Flash时钟,K7325T实测有效

本文深入解析了FPGA远程升级中的STARTUPE2原语应用,特别针对K7325T芯片的Flash时钟问题提供了实测有效的解决方案。通过详细的原语工作机制、Vivado工程实现步骤及调试技巧,帮助工程师规避常见陷阱,提升远程升级的可靠性和效率。

weixin_42558093的博客 141

09 Java多线程的状态详解 NEW, RUNNABLE, BLOCKED, WAITING, TIME_WAITING, TERMINATED,jstack查看dump文件中线程的状态

Java多线程的状态详解 NEW, RUNNABLE, BLOCKED, WAITING, TIME_WAITING, TERMINATED,jstack查看dump文件中线程的状态

思念十年后 293

USB的24脚和12脚TYPEC引脚定义

USB 是一种接口,用于将设备连接到计算机上。通过该连接,计算机可以向设备发送数据或检索来自设备的数据。USB 为开发者提供了一个标准接口,适用于多种不同类型的应用。借助于系统的设计过程,USB 设备易于连接和使用。本应用笔记会使该过程变得更加简单。以下是本应用笔记中会使用的概念:USB 历史USB 架构USB 物理接口USB 速度USB 电源USB 端点USB 通信协议USB 描述符USB 类设备USB 枚举与配置过程。

易板 1万+

Java线程的几种状态

目录 1、获取线程所有状态 2、打开values源码,展示线程的所有状态 3、状态转移图 4、线程状态详解 5、完整线程状态实例演示 1、获取线程所有状态 /** * 线程的状态 */ public class ThreadState { public static void main(String[] args) { printState();//打印所有状态 } /** * 打印所有状态 */ priv..

gaoyu0914的博客 196

Lumerical官方案例、FDTD时域有限差分法仿真学习(三)——环形谐振器(Ring resonator)之第二&第三部分

二、使用 MODE 的参数提取和蒙特卡罗 三、最终参数提取

weixin_45719141的博客 1万+

十二种嵌入式通信协议

嵌入式通信协议选型核心要点解析 在嵌入式系统开发中,通信协议选型直接影响项目成败。本文系统梳理了12种主流协议,从三个维度提供选型指南: 板级通信协议: I²C:双线制,适合传感器连接(温湿度、RTC等) SPI:高速全双工,适用于Flash存储、TFT屏等 UART:异步串行,常用于调试日志输出 总线通信协议: CAN:汽车电子首选,支持多主节点和硬件仲裁 LIN:低成本单线方案,适用于车窗控制等场景 RS485:工业自动化主力,最长传输距离1200米 Modbus:工业领域通用协议,支持RTU/TCP格

qq_37999850的博客 248

Java多线程Thread和Runnable(总结)

Java多线程常用的两种实现方法 继承Thread类 子类继承Thread类具备多线程能力 启动线程:子类对象.start() 不建议使用:避免OOP单继承局限性 实现Runnable接口 实现接口Runnable接口具有多线程能力 启动线程:传入目标对象+Thread对象.start() new Thread(new ClassImplementRunnable()).start() - *推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用。 一份资源,多个代理 /

DDDDeng_的博客 2498

【系统架构设计师】预测试卷一:综合知识(75道选择题)

软件架构风格是描述某一特定应用领域中系统组织方式的惯用模式,架构风格反映领域中众多系统所共有的结构和 (48),强调对架构(49)的重用。 (48) A. 语义特性 B. 功能需求 C. 质量属性 D. 业务规则 (49) A. 分析 B. 设计 C.实现

数据知道的博客 7766

java线程状态转换

java线程的“6种状态”是一个非常经典的面试考点,但这6种状态是定义在枚举中的的状态。,而 JVM 将 OS 的“就绪”和“运行”合并为 RUNNABLE,并细化了阻塞的维度,从而得到了这 6 种状态。下面是这 6 种状态的详细定义、生命周期以及最关键的状态切换路径解析。

qq_22925809的博客 280

UNet改进(23):如何用SLCAM模块提升UNet的分割性能

本文提出一种改进的UNet架构,通过引入轻量化注意力模块(SLCAM)增强医学图像分割性能。SLCAM整合了通道和空间双重注意力机制:通道注意力采用全局池化和共享MLP学习通道权重,空间注意力通过7×7卷积捕捉上下文信息。网络在传统UNet基础上,在每个下采样和上采样块后加入SLCAM模块,并保持跳跃连接结构。实验表明,该方法能自适应聚焦重要特征,在不显著增加计算负担的情况下提升分割精度,特别适用于医学图像等需精确边界的场景。未来可探索三维注意力、动态参数调整等改进方向。

2501_91516851的博客 438

RT-Thread Studio 使用笔记(七)| 配合STM32CubeMX添加裸机驱动(以ADC为例)

1. 配置STM32CubeMX 2. 复制stm32xxxx_hal_msp.c文件 将 CubeMx 生成的代码 stm32f7xx_hal_msp.c 函数复制到 RT-Thread Studio 生成的工程中,并参与工程编译: 由于我们并没有使用 CubeMx 生成的工程,所以这里需要将 stm32f7xx_hal_msp.c 文件中 #include “main.h” 替换为 #i...

Mculover666的博客(嵌入式) 9626

Java线程的生命周期

Java线程的生命周期

weixin_62570753的博客 263

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

多线程:6种状态及其转换条件

根据Thread类的源码,Java线程共有6种状态。

Y20051106的博客 528

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

Java线程生命周期与状态转换详解

线程是操作系统进行CPU调度的基本单位,Java通过Thread类封装了线程操作。线程生命周期包含NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING和TERMINATED六种状态,状态转换涉及操作系统调度和JVM内存模型。理解线程状态转换机制对开发高性能并发程序至关重要,特别是在处理线程同步、锁竞争和资源等待等场景时。通过jstack等工具可以监控线程状态,诊断死锁、线程泄漏等问题。合理使用线程池和并发工具类能有效管理线程生命周期,提升系统吞吐量。本文深入解析Java线程状

aodiyi6351的博客 347

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

Java线程池

1.线程池 1.1 线程状态 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。线程对象在不同的时期有不同的状态。那么Java中的线程存在哪几种状态呢?Java中的线程 状态被定义在了java.lang.Thread.State枚举类中,State枚举类的源码如下: public class Thread { public enum State { /* 新建 */ NEW , /* 可运行状态 *

nuuih的博客 133

Java中的线程安全锁ObjectLocked及其作用详解

本文是作者结合多年Java面试经验整理的高频考点清单,涵盖从基础语法到分布式系统的核心问题。除了标准答案外,还提供避坑指南和面试官视角的加分点,帮助读者在竞争中脱颖而出。文中附代码示例和性能优化对比,适合有一定项目经验的开发者查漏补缺。

蓝鲸鱼计算机软件开发工作室 571

漫话:如何给女朋友解释为什么Java线程没有Running状态?

在多线程操作系统中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体。线程是有状态的,线程的状态被定义在Thread.State枚举中,在Java...

HollisChuang's Blog 451

如何给女朋友解释为什么Java线程没有Running状态?

在多线程操作系统中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体。线程是有状态的,线程的状态被定义在Thread.State枚举中,在Java Doc中也有明确的解释:通过查看源码以及阅读Java Doc,我们可以知道,线程主要有以下6种状态:NEW当一个线程被创建出来的,但是还没调用start()方法的时候,他处于NEW状态。RUNNABLE在Java虚拟机中执行的线程处于这种状态BLOCKED正在等待锁的阻塞线程处于这种状态。WAITING不确定地等待另一个

知识分享官 144

java线程runnable_Java 线程状态之 RUNNABLE

需要强调的是,这里谈论的是Java虚拟机层面所暴露给我们的状态,与操作系统底层的线程状态是两个不同层面的事。具体而言,这里说的 Java 线程状态均来自于 Thread 类下的 State 这一内部枚举类中所定义的状态:public enum State {NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED;}什么是 RUNNABLE?直接看...

weixin_36201981的博客 453

线程的状态

线程在不同的时期存在不同的状态,状态信息存在中。状态类信息。状态解释。调用与线程有关的方法是造成线程状态改变的主要原因,因果关系如图所示。从图中可知,在调用与线程有关的方法后,线程会进入不同的状态。这些状态之间有些是双向切换,比如 WAITING 和 RUNNING 状态之间可以循环地进行切换,而有些是单向切换,比如线程销毁 ( TERMINATED 状态)后不能自动进入 RUNNING 状态。下面对 6 种线程状态用程序代码的方式进行验证。

m0_62943934的博客 909

多线程Thread与Runnable 以及五种状态

多线程Thread与Runnable 以及五种状态 1、新建状态(New):新创建了一个线程对象。private static void showThread() { //建立线程对象 MyThread myThread = new MyThread(); //开启线程 myThread.start(); }2、就绪状态(Ru

lyc6681的博客 1715
上一篇: Locked ownable synchronizers(转)
下一篇: jconsole远程连接
MrMoving
博客等级 码龄10年 54粉丝 145原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值