-
概述
- 线程状态
- 状态转换
-
背景
-
了解了 线程的相关操作
-
启动
- start
-
同步相关
- wait
- notify
- notifyAll
-
时间片相关
- sleep
- yield
- join
-
-
看上去操作多, 关系复杂
- 但实际上, 线程有着清晰的 生命周期
- 可以用一个 状态机 去描述这个东西
- 抱歉, 我没学好
-
1. 线程
-
概述
- 线程
- 这里指的, 是 Java 进程
- 线程
-
线程
-
进程中有顺序控制流
-
进程中
- 线程无法独立存在
- 线程必须依托于 进程
- 当然既然依托 进程
- 必定就是 运行时 才会有的概念
-
有顺序
- 进程的执行, 是有顺序的
- 通常的顺序, 就是 程序的 调用链
- 以栈的形式来处理
- 每一个 方法, 会有一个自己的 栈帧
- 栈帧 存储局部变量
- 栈帧 记录着方法结束后返回哪里
- 栈帧 也向 程序技术器 传递下次执行代码的地址
- 方法内的执行的顺序, 则是根据 代码 来的
- 以栈的形式来处理
-
控制流
- 当进程内的代码一行一行执行, 就形成了一个 控制流
- 执行的工作, 是交给 执行引擎 来做的
- 当进程内的代码一行一行执行, 就形成了一个 控制流
-
-
线程的资源
-
线程共享的资源
- 堆内存
-
线程各自的资源
- 栈
- 程序计数器
- 本地方法栈
- 执行引擎
- 或者说, Java 线程的本质, 其实就是 执行引擎
-
-
-
后续
- 线程还有很多其他的点, 这个随缘再讲吧
2. 线程所需的资源
-
概述
- 线程相关的资源
-
资源
- 线程工作的时候, 肯定是需要资源的
- 具体需要那些资源呢?
- 线程工作的时候, 肯定是需要资源的
1. 物理资源
- 概述
- 物理方面的资源
- 这方面资源, 可能没有那么抽象
1. 代码
-
概述
- 代码
-
代码
- 线程的工作, 就是执行代码
- 需要代码, 才会有线程存在
- 线程的工作, 就是执行代码
2. JVM 内存
-
概述
- 内存空间
-
内存空间
-
栈
- 线程运行的空间
- 每个线程, 都会有一个属于自己的栈
- 每一次方法调用, 都会有一个 栈帧
- 栈内的数据, 由 线程独享
-
堆
- 如果用到 引用类型对象, 是需要堆的
-
元数据区
- 以前的方法区
- 会存放很多 代码相关的内容, 比如 Class 对象
- 这个以后会介绍
-
2. 逻辑资源
- 概述
- 稍微抽象一些的资源
1. CPU 时间片
-
概述
- CPU 时间片
-
CPU 时间片
- 所有程序的运行, 最终都是交给 CPU 来完成的
- CPU 相对其他硬件, 快了太多
- 所以 CPU 把自己的时间, 分成了很多小片
- 通过调度机制, 将小片分给不同的线程来交替使用
- 这样既利用了 CPU 的性能, 又达到了 并发
- 当然也产生了 线程调度 和 线程安全 等问题
-
调度
- 决定运行哪个线程的一些列机制
- 设计逻辑 和 调度因素 就不说了, 我没学好...
- 决定运行哪个线程的一些列机制
-
线程的角度
- 线程想要运行, 首先需要集齐其他资源
- 后面会提到
- 然后就是 等待调度系统, 来分配时间片
- 线程执行
- 运行完时间片之后, 或者主动让出之后, 等待下一轮的调度
- 线程想要运行, 首先需要集齐其他资源
2. 锁
-
概述
- 锁
-
锁
-
执行同步代码的相关许可
- 例如之前了解的 监视器锁
-
如果没有锁, 则
- 进入 等待队列
- 或者 进入阻塞状态
- 这个时候, 会让出 CPU 资源
- 等锁被释放了, 再去等待分配
- 当然这个算是 线程调度 的内容, 我没学好...
-
3. 其他资源
-
概述
- 其他资源
-
其他资源
- 通常是一些 IO 或者 其他线程相关 的资源
-
因为计算机各个部分的速度, 是不一样的
# 这个排行有点混, 网卡也可能走 PCIE CPU > Cache > Memory > PCIE > SATA > Network
-
栈处在 内存 这一块, 所以等待 其他IO, 会比较常见
-
- 通常是一些 IO 或者 其他线程相关 的资源
3. 大致流程
-
概述
- 简单串下大致流程
-
简单流程
-
流程
- 写好线程代码
- 启动 JVM, 加载代码, 并给线程分配到 栈 空间, 以及相关的 堆空间
- 线程准备 好其他资源, 就绪, 等待调度
- 线程获得 CPU 时间片, 执行
- 线程获得 锁 继续执行
- 线程执行完毕, 归还锁
- 线程 时间片 耗尽, 重新开始等待调度
-
简单
- 虽然有 锁 这个环节, 但这个流程已经算是 简单清晰了
-
-
运行时 资源
-
资源
- CPU 时间
- 锁
- 其他资源
-
内存
- 内存其实也是 运行时 的东西
- 但是正常情况下, 内存分配不会太影响 线程调度
- 内存其实也是 运行时 的东西
-
-
想法
- 线程调度 与 状态转变
- 本质上还是 资源的分配
- 调度是 CPU 资源的分配
- 阻塞后放弃时间片, 实际也是对 CPU 资源的一种节省
- 还有由 IO 触发的一些等待, 目的还是减少 CPU 的浪费
- 本质上还是 资源的分配
- 线程调度 与 状态转变
3. 线程的状态
-
概述
- 线程的状态
-
工作阶段 - 基本
-
初始 - NEW
- 新创建线程实例
- 一个 Java 线程对象, 对应一个本地 OS 的用户态线程
- 创建好之后, 需要 start 方法, 才能执行
- start 后, 进入 可执行 阶段
- 新创建线程实例
-
可执行 - RUNNABLE
- 这个阶段下, 有 线程 有 两个状态
- 可执行
- 所有执行需要的条件, 都准备好了
- 但是因为 CPU 的调度问题, 还没有执行
- 执行中
- 正在执行
- 可执行
- 这个阶段下, 有 线程 有 两个状态
-
终止 - TERMINATED
- 正常终止
- 线程所有的工作都已经完成
- 异常终止
- 线程在执行中, 出现了无法处理的异常
- 终止状态线程 对应的对象, 依然存在
- 需要最后垃圾回收, 才能完事
- 正常终止
-
-
各种中断
-
概述
- 简述 中断
-
实际情况
- 很多时候, 一个线程不会一帆风顺的的执行完
- 中间会有一些其他的状态
- 很多时候, 一个线程不会一帆风顺的的执行完
-
原因
-
资源
-
概述
- 资源有限, 需要各种竞争
-
资源们
- CPU
- IO
- 监视器锁
-
-
协作
-
概述
- 多线程最初是不可预测的
- 但是通过一些 线程协助 的方法, 我们可以让线程的活动变得有序
-
协作
- 围绕 监视器锁
- wait
- notify
- notifyAll
- 围绕 时间片
- sleep
- yield
- join
- 围绕 监视器锁
-
-
-
-
其他状态
-
阻塞 - BLOCKED
- 等待 监视器锁
-
等待 - WAITING
- 进入等待状态
- 需要 其他线程 将其唤醒
- 进入等待状态
-
定时等待 - TIMED WAITING
- 进入 预定时间 的等待装填
- 不需要 其他线程将其唤醒
- 进入 预定时间 的等待装填
-
4. 状态转换
- 概述
- 各种状态间的转换
- 由于我不爱画图, 所以这个描述, 可能会比较的晦涩
- 第一次整理, 可能会有遗漏
- 如果发现, 欢迎补充
- 各种状态间的转换
1. 常规状态
- 概述
- 常规状态的转换, 感觉比较简单
- 所以直接介绍 状态之间 的转换
- 常规状态的转换, 感觉比较简单
1. 没有线程 -> 初始状态
-
准备
- 还没有线程
-
操作
- 创建线程实例
-
结果
- 线程创建完成
- 处于 初始状态
2. 初始状态 -> 可执行状态
-
准备
- 线程处于 初始状态
-
操作
- 线程对象的 Thread.start() 方法 被调用
- 其他相关的资源, 也要 准备就绪
-
结果
- Thread.start() 方法调用成功
- start 会调用 run 方法
- 线程进入 可执行状态
- Thread.start() 方法调用成功
3. 可执行状态 -> 终止状态
-
准备
- 线程处于 可执行状态
-
操作
- 线程的 Thread.run() 方法执行完毕
- 正常退出
-
结果
- 线程进入 终止状态
4. 终止状态 -> 线程结束
-
准备
- 线程进入终止状态
-
操作
- 垃圾回收被触发
- 回收线程的 堆, 栈, 计数器 资源
- 垃圾回收被触发
-
结果
- 线程结束
2. 中断状态
-
概述
- 主要描述 可执行状态 到 中断状态 的过程
-
描述方式
- 这些变换, 我会换一个维度来描述
- 直接描述 状态 之间的变化, 会比较复杂, 难以理解
- 状态到状态的变化, 可能会有 多种原因
- 但是 一个原因, 通常只会对应一种 状态变化
- 也不完全是一种, 但相对来说, 从 原因入手, 会清晰很多
- 直接描述 状态 之间的变化, 会比较复杂, 难以理解
- 这些变换, 我会换一个维度来描述
1. 可执行 -> 阻塞: 申请监视器锁失败
-
概述
- 获取 synchronized 对象失败
-
准备
- 线程处于 可执行状态
-
执行
- 尝试去 获取 监视器锁, 结果失败
- 线程进入 阻塞 状态
-
返回 可执行 状态
- 线程获得了 监视器锁
2. 可执行 -> 等待: 线程进入 等待状态
-
概述
- 进入等待状态
- 放弃所持的锁
- 等待唤醒
- 进入等待状态
-
准备
- 线程处于 可执行状态
- 线程已经成功获取了 某个对象的锁
-
执行
- 调用 Object.wait() 方法
- 这里是 Object 的方法, 而不是 Thread 的方法
- 这个操作的本质, 是放弃已经持有的 Object 对象的监视器锁
- 监视器锁 是每个实例自带的
- 调用 wait() 后, 进入实例的 等待队列
- 调用后, 视为 主动放弃 监视器锁
- 等待队列 这个后面再说吧
- 这里是 Object 的方法, 而不是 Thread 的方法
- 调用 Object.wait() 方法
-
返回 可执行 状态
-
Object.notify()/Object.notifyAll()
- 监视器锁的对象, 唤起 等待队列 里的 某一个/所有 线程
- 随机抽取一个幸运线程抢到锁
- 其他倒霉的线程, 继续处于 阻塞 状态
- 监视器锁的对象, 唤起 等待队列 里的 某一个/所有 线程
-
interrupt()
- 执行者
- 另外一个线程
- 持有 wait 中线程的实例
- 不需要持有 wait 中线程的任何锁
- wait 中线程
- wait 被打断
- 抛出 InterruptedException 异常
- 后续异常处理 就由具体实现来定了
- 执行者
-
3. 可执行 -> 等待 -> 可执行: Thread.sleep(int millis)
-
概述
- sleep
-
准备
- 线程处于 可执行状态
-
执行
- 执行方法的线程, 进入 定时等待 状态
- 时间由 参数决定
- 这玩意精度可能会没那么高, 所以不要太依赖那个时间
- 由 Thread.sleep 方法进入 定时等待, 不会释放任何 监视器锁
- 执行方法的线程, 进入 定时等待 状态
-
返回 可执行 状态
-
sleep 到时间
- 设定的时间到了
-
Thread.interrupt()
-
4. 可执行 -> 等待: Thread.join()
-
概述
- 进入 等待状态
- 等待另一个线程执行完
- 进入 等待状态
-
准备
- 线程处于 可执行状态
- 线程取得了 另一个线程的引用
- 线程可以获得 另一个线程的锁
-
执行
- 调用 另一个线程 的 join 方法
- 调用后 线程 进入等待状态
- 老实说, 这个方法的源码有点绕
- 本身是个 synchronized 的方法
- 执行的时候, 需要检验 子线程的 Alive 状态
- 最后调用的, 却是 自己的 wait() 方法
- 还需要 while 来占用 CPU 时间...
- 调用 另一个线程 的 join 方法
-
返回
-
子线程结束
- join 方法返回, 线程继续执行
-
interrupt()
- 类似 sleep
-
5. 可执行 -> 等待: I/O 处理
-
概述
- 由 I/O 处理引发的中断
-
准备
- 线程处于 可执行状态
- 线程需要 I/O 来载入 资源
-
执行
- 开始 读取内容
- 通常会进入 等待 状态
-
返回 可执行 状态
- 通常需要通过 I/O 来 interrupt 来返回
6. 可执行状态内部的转换
-
概述
- 可执行内部的转换
-
准备
- 线程处于 可执行状态
-
可执行状态 -> 执行状态
- 分配到了 时间片
-
执行状态 -> 可执行状态
- 时间片用完
- 当前线程调用了 yield 方法
- 放弃时间片, 重新回到 可执行状态
3. 后续
-
整理这个笔记, 感觉是个麻烦事
- 一开始以为, 只要简单说说线程, 说说状态就可以了
- 后来发现, 还要熟悉状态之间的关系
- 状态的转化
- 转化状态使用的方法
-
后续
- 多线程相关内容
- 多线程常用 工具
- 多线程 常用的处理方式...
- 多线程相关内容
ps
- ref
-
- jvm 执行引擎
- 卧槽我看这个干嘛?
- jvm 执行引擎
-
对于OpenJDK而言,是不是每个Java线程都对应一个执行引擎线程?
- jvm 执行引擎
- 卧槽我看这个干嘛?
- jvm 执行引擎
-
Java 核心技术(10th edition)
- 14 章
-
图解 Java 多线程设计模式
- 序章 1
-
Java 源代码
- Thread.State
- 这个 Enum 里有对 线程状态的约束
- Thread.State
-
- 这个系列讲多线程, 都挺好的
-
- 锁相关
-
- IO 这块
-