前言

看到网上关于线程状态转移的博客,好多都没说明白。查了很多资料,汇总一篇,希望通过这一篇,能把这些状态转移解释明白,如果有什么没考虑到的,希望指正

转载注明出处原文地址:https://www.cnblogs.com/darope/p/12748184.html

状态转移图

  • 要明白线程转移的详细过程,可以先通过一张图片,了解一个线程的生命周期中,该线程会处在何种状态:



    注意:单向箭头表示不可逆

1.0 新建态到就绪态

  • 概念:1. 新建态:一个线程被创建出来时候所处的状态 ;2. 就绪态:线程调用start()方法后,便处于可以被操作系统调度的状态,即就绪态。该状态可以由三处转化而来,新建态执行了start、线程阻塞结束、锁池等待队列中的线程获得了锁
  1. Thread t1 = new Thread(
  2. new Runnable() {
  3. @Override
  4. public void run() {
  5. for (int i = 0; i < 10; i++) {
  6. System.out.println("hello : " + i);
  7. }
  8. }
  9. }
  10. );
  11. // t1执行start()之后,处于就绪态,操作系统此时可以分配时间片给该线程,让该线程执行run方法体中的内容
  12. t1.start();
  • 该状态对应状态图中的第0步,比较简单,不再赘述

1.1 就绪态到运行态

  • 概念:运行态:表示当前线程被操作系统调度,分配了时间片,执行线程中的run方法时的状态。运行态只可以由就绪态的线程转化而来,如果多个线程都处在就绪态,就等待操作系统分配
  1. public static void main(String[] args) {
  2. // 线程1
  3. Thread t1 = new Thread(() -> {
  4. for (int i = 0; i < 10; i++) {
  5. System.out.println("t1 : running");
  6. }
  7. });
  8. t1.start();
  9. // 线程2
  10. Thread t2 = new Thread(() -> {
  11. for (int i = 0; i < 10; i++) {
  12. System.out.println("t2 : running");
  13. }
  14. });
  15. t2.start();
  16. }
  • 注:可以看到t1和t2两个线程都运行start()方法后,控制台会随机交叉打印两个线程的输出信息,这种随机,是操作系统随机分配时间片的调度决定的

1.2 运行态到就绪态

1.2.1 时间片用完

  • 我们知道,操作系统为了公平,不可能从就绪态里面选择一个,一直执行完,而是随机切换到另外的线程去执行,每个线程分配的执行时间结束,操作系统去调用别的线程,当前刚执行结束的线程便由运行态重新回到就绪态,等待操作系统的再次分配。参考上一个代码例子,t1的线程执行体方法中循环打印100次,t2也是,但是会看到控制台是交叉打印的,说明了这一点

1.2.2 t1.yield() 、Thread.yield();

  • 概念:在t1线程体中调用t1.yield(),和Thread.yield();本质上一样,Thread.yield()表示当前线程让渡。线程调用yield()方法,会让该线程重新回到就绪队列,但是yield()让当前线程回到就绪队列后,并不能保证操作系统再次调用不会选择该线程,所以yield()方法不能用来控制线程的执行顺序
  1. public static void main(String[] args) {
  2. // 线程1
  3. Thread t1 = new Thread(() -> {
  4. Thread.yield();
  5. for (int i = 0; i < 10; i++) {
  6. System.out.println("t1 : running " + i);
  7. }
  8. });
  9. t1.start();
  10. // 线程2
  11. Thread t2 = new Thread(() -> {
  12. for (int i = 0; i < 10; i++) {
  13. System.out.println("t2 : running " + i);
  14. }
  15. });
  16. t2.start();
  17. }
  • 注意:这个程序我故意把线程让步yield()方法写在线程体刚运行的时候,也就是说,每次操作系统分配给t1线程时间片时候,t1都会让步。但这次的让步不代表t1接下来的方法不会执行,也就是我让步之后,大家再一起抢,t1又抢到了时间片,那么t1本次时间片内便执行接下来的方法,等时间片结束,再次分配t1时间片,t1还会让,再接着抢,抢到和抢不到都有可能。

1.3 运行态到阻塞态

  • 概念:阻塞态表示当前线程被由于某种原因,被挂起,也就是被阻塞,正在运行的线程被阻塞后,即使结束阻塞状态也回不去运行态,只能回到就绪态,等待os分配cpu资源去调度

1.3.1 Thread.sleep()

  1. public static void main(String[] args) {
  2. Thread t1 = new Thread(() -> {
  3. for (int i = 0; i < 10; i++) {
  4. try {
  5. Thread.sleep(1000);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. System.out.println("hello : " + i);
  10. }
  11. }
  12. );
  13. // t1执行start()之后,处于就绪态,操作系统此时可以分配时间片给该线程
  14. t1.start();
  15. }
  • 注意:让当前线程睡眠,该线程被阻塞,睡眠时间结束,该线程接着运行

1.3.2 t2.join()

  • 当在t1中调用t2.join()。那么t1会阻塞,一直等待t2执行完毕,才结束阻塞回到就绪态
  • 直接看代码:这里我把t1和t2抽出来当做全局静态变量
  1. public class TestThread {
  2. static Thread t1;
  3. static Thread t2;
  4. public static void main(String[] args) {
  5. // 线程1
  6. t1 = new Thread(() -> {
  7. for (int i = 0; i < 100; i++) {
  8. if(i == 50) {
  9. try {
  10. t2.join();
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. System.out.println("t1 : running " + i);
  16. }
  17. });
  18. t1.start();
  19. // 线程2
  20. t2 = new Thread(() -> {
  21. for (int i = 0; i < 100; i++) {
  22. System.out.println("t2 : running " + i);
  23. }
  24. });
  25. t2.start();
  26. }
  27. }
  • 解释:这个程序的运行结果是,首先t1,t2挣抢时间片,按系统调度,首先控制台t1和t2都有打印自身的输出信息,当t1执行到i=50的时候,调用了t2.join()。此时控制台会全部打印t2的信息,一直等待t2的循环结束,执行体的run方法结束,再去打印t1剩下的没运行完的循环
  • 所以join的流程可以抽象为下面这张图片

1.3.3 t1等待用户输入,等待键盘响应

这个很好理解,比如你就执行一个main函数的主线程,等待输入时,该线程是不会结束的,就是处于阻塞状态。

1.4 阻塞态到就绪态

  • 1.3中所有阻塞态结束,比如sleep结束,join后t2执行结束,用户输入了信息回车等。t1会结束阻塞态,但是都是回到就绪态,无法再立即回到运行态

1.5 运行态到等待队列

这里牵扯到对象锁的概念

  • 两个线程竞争锁,其中t1释放锁,也就是把所占有的对象锁让出。那么如果不主动唤醒,该线程一直处在等待队列中,得不到操作系统OS的调度
  • 概念:等待队列,就是当前线程占有锁之后,主动把锁让出,使自身进入等待队列。此种wait加notify可以保证线程执行的先后顺序。notify()是通知一个等待队列的线程回到锁池队列。notifyAll()是通知所有处在等待队列的线程,都回到锁池队列。
  • show me code:
  1. public static void main(String[] args) {
  2. // 准备上锁的对象
  3. Object o = new Object();
  4. // 线程1
  5. Thread t1 = new Thread(() -> {
  6. synchronized (o) {
  7. for (int i = 0; i < 10; i++) {
  8. try {
  9. if(i == 5) {
  10. // 当i=5的时候,让出对象锁,t1进入等待队列
  11. // 如果没人通知,t1一直等待,程序不会结束
  12. o.wait();
  13. }
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. System.out.println("t1 : running " + i);
  18. }
  19. }
  20. });
  21. t1.start();
  22. // 线程2
  23. Thread t2 = new Thread(() -> {
  24. synchronized (o) {
  25. for (int i = 0; i < 10; i++) {
  26. System.out.println("t2 : running " + i);
  27. }
  28. // 这里t2得到锁,执行完线程方法之后一定要通知t1停止等待。
  29. // 不然t1结束不了,处在一直等待通知的状态
  30. o.notify();
  31. }
  32. });
  33. t2.start();
  34. }

1.6 运行态到锁池队列

  • 参考1.5的程序,在i=5之前,t1占有该对象锁,t2即使start()也得不到运行,原因是该对象锁被t1占有,t2拿不到,所以就进入锁池队列

1.7 等待队列到锁池队列

  • 参考1.5的程序,当t1wait之后,让出对象锁,t1进入了等待队列,t2拿到锁,运行完之后,调用notify()让等待队列中的t1进入锁池队列。

1.8 锁池队列到就绪态

  • 参考1.5的程序,当t2结束后,通知t1进入锁池队列,t2由于运行结束,处在锁池队列中的t1可以拿到对象锁,进入就绪态,等待操作系统的调度,从而进入运行态

1.9 运行态到死亡态

死亡态不可逆,一旦线程进入死亡态,就再也回不到其他状态

  • 死亡态只能由运行态进入,运行态中的线程。例如通过操作系统的不停调度,t1直到把整个run方法中的循环体执行完毕,该线程完成了它的使命,便进入死亡态

深入理解Java线程状态转移的更多相关文章

  1. Java线程状态Jstack线程状态BLOCKED/TIMED_WAITING/WAITING解释

    一.线程5种状态 新建状态(New) 新创建了一个线程对象. 就绪状态(Runnable) 线程对象创建后,其他线程调用了该对象的start()方法.该状态的线程位于可运行线程池中,变得可运行,等待获 ...

  2. Java线程状态转换

    前言:对于Java线程状态方面的知识点,笔者总感觉朦朦胧胧,趁着最近整理资料,将Java线程状态方面的知识点总结归纳,以便加深记忆. 1.Java线程状态值 在Thread类源码中通过枚举为线程定义了 ...

  3. Java线程状态及 wait、sleep、join、interrupt、yield等的区别

    Java中的线程状态(详见Java线程状态及转换-MarchOn): wait:Object类的实例方法,释放CPU执行权,进入等待状态,直到  被中断.被拥有该对象锁的线程唤醒(notify或not ...

  4. 深入理解Java线程池:ScheduledThreadPoolExecutor

    介绍 自JDK1.5开始,JDK提供了ScheduledThreadPoolExecutor类来支持周期性任务的调度.在这之前的实现需要依靠Timer和TimerTask或者其它第三方工具来完成.但T ...

  5. Java线程状态、线程start方法源码、多线程、Java线程池、如何停止一个线程

    下面将依次介绍: 1. 线程状态.Java线程状态和线程池状态 2. start方法源码 3. 什么是线程池? 4. 线程池的工作原理和使用线程池的好处 5. ThreadPoolExecutor中的 ...

  6. 深入理解 Java 线程池

    一.简介 什么是线程池 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务. 为什么要用线程池 如果并发请求数量很多,但每个线程执行的时间很短,就会出现频繁的创建 ...

  7. JVM探秘:jstack查看Java线程状态

    本系列笔记主要基于<深入理解Java虚拟机:JVM高级特性与最佳实践 第2版>,是这本书的读书笔记. jstack命令可以打印Java进程的各个线程堆栈跟踪信息,可以用来查看Java中各个 ...

  8. 浅谈 Java线程状态转换及控制

    线程的状态(系统层面) 一个线程被创建后就进入了线程的生命周期.在线程的生命周期中,共包括新建(New).就绪(Runnable).运行(Running).阻塞(Blocked)和死亡(Dead)这五 ...

  9. Java线程状态及切换

    Java线程状态及切换 一.什么是Java线程状态 在Java程序中,用于描述Java线程的六种状态: 新建(NEW):当前线程,刚刚新建出来,尚未启动. 运行(RUNNABLE):当前线程,处于竞争 ...

随机推荐

  1. 金融和IT的区别

    在进入金融圈之前, 我写了十五年的代码, 在San Francisco Bay Area(也就是中国人所说的硅谷)工作过两三年. 去年因为Fintech和香港.NET俱乐部的缘故, 我接触了私人银行和 ...

  2. 为什么你的程序配了classpath还是找不到类

    classpath简介 classpath是java程序时拥有的一个系统变量,这个变量可以通过如下方式获取 System.out.println(System.getProperty("ja ...

  3. springboot创建

    1.点击File----->New----->Project...  2.输入MAVEN,组名.包名等相关参数  3.选择SpringBoot版本,选择项目需要依赖的相关骨架包  4.设置 ...

  4. OpenCV-Python 图像梯度 | 十八

    目标 在本章中,我们将学习: 查找图像梯度,边缘等 我们将看到以下函数:cv.Sobel(),cv.Scharr(),cv.Laplacian()等 理论 OpenCV提供三种类型的梯度滤波器或高通滤 ...

  5. iOS 性能优化收集

    iOS 性能调试 instrument Instrument Instrument之Core Animation工具 避免图层混合 ①.确保控件的opaque属性设置为true,确保backgroun ...

  6. ubuntu查看并杀死自己之前运行的进程解决办法RuntimeError: CUDA error: out of memory

    问题描述:在跑深度学习算法的时候,发现服务器上只有自己在使用GPU,但使用GPU总是会报RuntimeError: CUDA error: out of memory,这是因为自己之前运行的进程还存在 ...

  7. Conda安装包错误-CondaHTTPError: HTTP 000 CONNECTION FAILED for url <https://conda.anaconda.org/r/win-64/repodata.json> Elapsed:

    可能是防火墙问题:conda config --set ssl_verify false 安装 openssl . 换源: cmd输入conda config --add channels r 进入C ...

  8. 并查集例题02.带权并查集(poj1182)

    Description 动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形.A吃B, B吃C,C吃A.现有N个动物,以1-N编号.每个动物都是A,B,C中的一种,但是我们并不知道它到底 ...

  9. Java 添加、读取和删除 Excel 批注

    批注是一种富文本注释,常用于为指定的Excel单元格添加提示或附加信息. Free Spire.XLS for Java 为开发人员免费提供了在Java应用程序中对Excel文件添加和操作批注的功能. ...

  10. SWUST OJ 1012哈希表(链地址法处理冲突)

    哈希表(链地址法处理冲突) 1000(ms) 10000(kb) 2676 / 6911 采用除留余数法(H(key)=key %n)建立长度为n的哈希表,处理冲突用链地址法.建立链表的时候采用尾插法 ...