Java - JVM - 线程状态

Posted 轩辕拾銉

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java - JVM - 线程状态相关的知识,希望对你有一定的参考价值。

  1. 概述

    1. 线程状态
    2. 状态转换
  2. 背景

    1. 了解了 线程的相关操作

      1. 启动

        1. start
      2. 同步相关

        1. wait
        2. notify
        3. notifyAll
      3. 时间片相关

        1. sleep
        2. yield
        3. join
    2. 看上去操作多, 关系复杂

      1. 但实际上, 线程有着清晰的 生命周期
      2. 可以用一个 状态机 去描述这个东西
        1. 抱歉, 我没学好

1. 线程

  1. 概述

    1. 线程
      1. 这里指的, 是 Java 进程
  2. 线程

    1. 进程中有顺序控制流

      1. 进程中

        1. 线程无法独立存在
        2. 线程必须依托于 进程
        3. 当然既然依托 进程
          1. 必定就是 运行时 才会有的概念
      2. 有顺序

        1. 进程的执行, 是有顺序的
        2. 通常的顺序, 就是 程序的 调用链
          1. 以栈的形式来处理
            1. 每一个 方法, 会有一个自己的 栈帧
            2. 栈帧 存储局部变量
            3. 栈帧 记录着方法结束后返回哪里
            4. 栈帧 也向 程序技术器 传递下次执行代码的地址
          2. 方法内的执行的顺序, 则是根据 代码 来的
      3. 控制流

        1. 当进程内的代码一行一行执行, 就形成了一个 控制流
          1. 执行的工作, 是交给 执行引擎 来做的
    2. 线程的资源

      1. 线程共享的资源

        1. 堆内存
      2. 线程各自的资源

        1. 程序计数器
        2. 本地方法栈
        3. 执行引擎
          1. 或者说, Java 线程的本质, 其实就是 执行引擎
  3. 后续

    1. 线程还有很多其他的点, 这个随缘再讲吧

2. 线程所需的资源

  1. 概述

    1. 线程相关的资源
  2. 资源

    1. 线程工作的时候, 肯定是需要资源的
      1. 具体需要那些资源呢?

1. 物理资源

  1. 概述
    1. 物理方面的资源
    2. 这方面资源, 可能没有那么抽象

1. 代码

  1. 概述

    1. 代码
  2. 代码

    1. 线程的工作, 就是执行代码
      1. 需要代码, 才会有线程存在

2. JVM 内存

  1. 概述

    1. 内存空间
  2. 内存空间

      1. 线程运行的空间
      2. 每个线程, 都会有一个属于自己的栈
      3. 每一次方法调用, 都会有一个 栈帧
      4. 栈内的数据, 由 线程独享
      1. 如果用到 引用类型对象, 是需要堆的
    1. 元数据区

      1. 以前的方法区
      2. 会存放很多 代码相关的内容, 比如 Class 对象
      3. 这个以后会介绍

2. 逻辑资源

  1. 概述
    1. 稍微抽象一些的资源

1. CPU 时间片

  1. 概述

    1. CPU 时间片
  2. CPU 时间片

    1. 所有程序的运行, 最终都是交给 CPU 来完成的
    2. CPU 相对其他硬件, 快了太多
      1. 所以 CPU 把自己的时间, 分成了很多小片
      2. 通过调度机制, 将小片分给不同的线程来交替使用
      3. 这样既利用了 CPU 的性能, 又达到了 并发
      4. 当然也产生了 线程调度 和 线程安全 等问题
  3. 调度

    1. 决定运行哪个线程的一些列机制
      1. 设计逻辑 和 调度因素 就不说了, 我没学好...
  4. 线程的角度

    1. 线程想要运行, 首先需要集齐其他资源
      1. 后面会提到
    2. 然后就是 等待调度系统, 来分配时间片
    3. 线程执行
    4. 运行完时间片之后, 或者主动让出之后, 等待下一轮的调度

2. 锁

  1. 概述

    1. 执行同步代码的相关许可

      1. 例如之前了解的 监视器锁
    2. 如果没有锁, 则

      1. 进入 等待队列
      2. 或者 进入阻塞状态
        1. 这个时候, 会让出 CPU 资源
        2. 等锁被释放了, 再去等待分配
        3. 当然这个算是 线程调度 的内容, 我没学好...

3. 其他资源

  1. 概述

    1. 其他资源
  2. 其他资源

    1. 通常是一些 IO 或者 其他线程相关 的资源
      1. 因为计算机各个部分的速度, 是不一样的

        # 这个排行有点混, 网卡也可能走 PCIE
        CPU > Cache > Memory > PCIE > SATA > Network
        
      2. 栈处在 内存 这一块, 所以等待 其他IO, 会比较常见

3. 大致流程

  1. 概述

    1. 简单串下大致流程
  2. 简单流程

    1. 流程

      1. 写好线程代码
      2. 启动 JVM, 加载代码, 并给线程分配到 栈 空间, 以及相关的 堆空间
      3. 线程准备 好其他资源, 就绪, 等待调度
      4. 线程获得 CPU 时间片, 执行
      5. 线程获得 锁 继续执行
      6. 线程执行完毕, 归还锁
      7. 线程 时间片 耗尽, 重新开始等待调度
    2. 简单

      1. 虽然有 锁 这个环节, 但这个流程已经算是 简单清晰了
  3. 运行时 资源

    1. 资源

      1. CPU 时间
      2. 其他资源
    2. 内存

      1. 内存其实也是 运行时 的东西
        1. 但是正常情况下, 内存分配不会太影响 线程调度
  4. 想法

    1. 线程调度 与 状态转变
      1. 本质上还是 资源的分配
        1. 调度是 CPU 资源的分配
        2. 阻塞后放弃时间片, 实际也是对 CPU 资源的一种节省
        3. 还有由 IO 触发的一些等待, 目的还是减少 CPU 的浪费

3. 线程的状态

  1. 概述

    1. 线程的状态
  2. 工作阶段 - 基本

    1. 初始 - NEW

      1. 新创建线程实例
        1. 一个 Java 线程对象, 对应一个本地 OS 的用户态线程
      2. 创建好之后, 需要 start 方法, 才能执行
        1. start 后, 进入 可执行 阶段
    2. 可执行 - RUNNABLE

      1. 这个阶段下, 有 线程 有 两个状态
        1. 可执行
          1. 所有执行需要的条件, 都准备好了
          2. 但是因为 CPU 的调度问题, 还没有执行
        2. 执行中
          1. 正在执行
    3. 终止 - TERMINATED

      1. 正常终止
        1. 线程所有的工作都已经完成
      2. 异常终止
        1. 线程在执行中, 出现了无法处理的异常
      3. 终止状态线程 对应的对象, 依然存在
        1. 需要最后垃圾回收, 才能完事
  3. 各种中断

    1. 概述

      1. 简述 中断
    2. 实际情况

      1. 很多时候, 一个线程不会一帆风顺的的执行完
        1. 中间会有一些其他的状态
    3. 原因

      1. 资源

        1. 概述

          1. 资源有限, 需要各种竞争
        2. 资源们

          1. CPU
          2. IO
          3. 监视器锁
      2. 协作

        1. 概述

          1. 多线程最初是不可预测的
          2. 但是通过一些 线程协助 的方法, 我们可以让线程的活动变得有序
        2. 协作

          1. 围绕 监视器锁
            1. wait
            2. notify
            3. notifyAll
          2. 围绕 时间片
            1. sleep
            2. yield
            3. join
  4. 其他状态

    1. 阻塞 - BLOCKED

      1. 等待 监视器锁
    2. 等待 - WAITING

      1. 进入等待状态
        1. 需要 其他线程 将其唤醒
    3. 定时等待 - TIMED WAITING

      1. 进入 预定时间 的等待装填
        1. 不需要 其他线程将其唤醒

4. 状态转换

  1. 概述
    1. 各种状态间的转换
      1. 由于我不爱画图, 所以这个描述, 可能会比较的晦涩
    2. 第一次整理, 可能会有遗漏
      1. 如果发现, 欢迎补充

1. 常规状态

  1. 概述
    1. 常规状态的转换, 感觉比较简单
      1. 所以直接介绍 状态之间 的转换

1. 没有线程 -> 初始状态

  1. 准备

    1. 还没有线程
  2. 操作

    1. 创建线程实例
  3. 结果

    1. 线程创建完成
    2. 处于 初始状态

2. 初始状态 -> 可执行状态

  1. 准备

    1. 线程处于 初始状态
  2. 操作

    1. 线程对象的 Thread.start() 方法 被调用
    2. 其他相关的资源, 也要 准备就绪
  3. 结果

    1. Thread.start() 方法调用成功
      1. start 会调用 run 方法
    2. 线程进入 可执行状态

3. 可执行状态 -> 终止状态

  1. 准备

    1. 线程处于 可执行状态
  2. 操作

    1. 线程的 Thread.run() 方法执行完毕
    2. 正常退出
  3. 结果

    1. 线程进入 终止状态

4. 终止状态 -> 线程结束

  1. 准备

    1. 线程进入终止状态
  2. 操作

    1. 垃圾回收被触发
      1. 回收线程的 堆, 栈, 计数器 资源
  3. 结果

    1. 线程结束

2. 中断状态

  1. 概述

    1. 主要描述 可执行状态 到 中断状态 的过程
  2. 描述方式

    1. 这些变换, 我会换一个维度来描述
      1. 直接描述 状态 之间的变化, 会比较复杂, 难以理解
        1. 状态到状态的变化, 可能会有 多种原因
        2. 但是 一个原因, 通常只会对应一种 状态变化
          1. 也不完全是一种, 但相对来说, 从 原因入手, 会清晰很多

1. 可执行 -> 阻塞: 申请监视器锁失败

  1. 概述

    1. 获取 synchronized 对象失败
  2. 准备

    1. 线程处于 可执行状态
  3. 执行

    1. 尝试去 获取 监视器锁, 结果失败
    2. 线程进入 阻塞 状态
  4. 返回 可执行 状态

    1. 线程获得了 监视器锁

2. 可执行 -> 等待: 线程进入 等待状态

  1. 概述

    1. 进入等待状态
      1. 放弃所持的锁
      2. 等待唤醒
  2. 准备

    1. 线程处于 可执行状态
    2. 线程已经成功获取了 某个对象的锁
  3. 执行

    1. 调用 Object.wait() 方法
      1. 这里是 Object 的方法, 而不是 Thread 的方法
        1. 这个操作的本质, 是放弃已经持有的 Object 对象的监视器锁
        2. 监视器锁 是每个实例自带的
      2. 调用 wait() 后, 进入实例的 等待队列
        1. 调用后, 视为 主动放弃 监视器锁
        2. 等待队列 这个后面再说吧
  4. 返回 可执行 状态

    1. Object.notify()/Object.notifyAll()

      1. 监视器锁的对象, 唤起 等待队列 里的 某一个/所有 线程
        1. 随机抽取一个幸运线程抢到锁
        2. 其他倒霉的线程, 继续处于 阻塞 状态
    2. interrupt()

      1. 执行者
        1. 另外一个线程
        2. 持有 wait 中线程的实例
        3. 不需要持有 wait 中线程的任何锁
      2. wait 中线程
        1. wait 被打断
        2. 抛出 InterruptedException 异常
          1. 后续异常处理 就由具体实现来定了

3. 可执行 -> 等待 -> 可执行: Thread.sleep(int millis)

  1. 概述

    1. sleep
  2. 准备

    1. 线程处于 可执行状态
  3. 执行

    1. 执行方法的线程, 进入 定时等待 状态
      1. 时间由 参数决定
      2. 这玩意精度可能会没那么高, 所以不要太依赖那个时间
    2. 由 Thread.sleep 方法进入 定时等待, 不会释放任何 监视器锁
  4. 返回 可执行 状态

    1. sleep 到时间

      1. 设定的时间到了
    2. Thread.interrupt()

4. 可执行 -> 等待: Thread.join()

  1. 概述

    1. 进入 等待状态
      1. 等待另一个线程执行完
  2. 准备

    1. 线程处于 可执行状态
    2. 线程取得了 另一个线程的引用
    3. 线程可以获得 另一个线程的锁
  3. 执行

    1. 调用 另一个线程 的 join 方法
      1. 调用后 线程 进入等待状态
      2. 老实说, 这个方法的源码有点绕
        1. 本身是个 synchronized 的方法
        2. 执行的时候, 需要检验 子线程的 Alive 状态
        3. 最后调用的, 却是 自己的 wait() 方法
          1. 还需要 while 来占用 CPU 时间...
  4. 返回

    1. 子线程结束

      1. join 方法返回, 线程继续执行
    2. interrupt()

      1. 类似 sleep

5. 可执行 -> 等待: I/O 处理

  1. 概述

    1. 由 I/O 处理引发的中断
  2. 准备

    1. 线程处于 可执行状态
    2. 线程需要 I/O 来载入 资源
  3. 执行

    1. 开始 读取内容
    2. 通常会进入 等待 状态
  4. 返回 可执行 状态

    1. 通常需要通过 I/O 来 interrupt 来返回

6. 可执行状态内部的转换

  1. 概述

    1. 可执行内部的转换
  2. 准备

    1. 线程处于 可执行状态
  3. 可执行状态 -> 执行状态

    1. 分配到了 时间片
  4. 执行状态 -> 可执行状态

    1. 时间片用完
    2. 当前线程调用了 yield 方法
      1. 放弃时间片, 重新回到 可执行状态

3. 后续

  1. 整理这个笔记, 感觉是个麻烦事

    1. 一开始以为, 只要简单说说线程, 说说状态就可以了
    2. 后来发现, 还要熟悉状态之间的关系
      1. 状态的转化
      2. 转化状态使用的方法
  2. 后续

    1. 多线程相关内容
      1. 多线程常用 工具
      2. 多线程 常用的处理方式...

ps

  1. ref
    1. Execution Engine

      1. jvm 执行引擎
        1. 卧槽我看这个干嘛?
    2. 对于OpenJDK而言,是不是每个Java线程都对应一个执行引擎线程?

      1. jvm 执行引擎
        1. 卧槽我看这个干嘛?
    3. Java 核心技术(10th edition)

      1. 14 章
    4. 图解 Java 多线程设计模式

      1. 序章 1
    5. Java 源代码

      1. Thread.State
        1. 这个 Enum 里有对 线程状态的约束
    6. Java多线程系列--“基础篇”08之 join()

      1. 这个系列讲多线程, 都挺好的
    7. 简谈Java的join()方法

      1. 锁相关
    8. Java 线程状态之 RUNNABLE

      1. IO 这块

以上是关于Java - JVM - 线程状态的主要内容,如果未能解决你的问题,请参考以下文章

Java 线程(JVM 层面)的生命周期

java 简单的代码片段,展示如何将javaagent附加到运行JVM进程

从JVM的角度看JAVA代码--代码优化

Java线程及Jvm监控工具

分析定位占用CPU资源高的JVM线程

内核线程Java线程与内核线程区别