深入理解Java线程状态转移
前言
看到网上关于线程状态转移的博客,好多都没说明白。查了很多资料,汇总一篇,希望通过这一篇,能把这些状态转移解释明白,如果有什么没考虑到的,希望指正
转载注明出处原文地址:https://www.cnblogs.com/darope/p/12748184.html
状态转移图
- 要明白线程转移的详细过程,可以先通过一张图片,了解一个线程的生命周期中,该线程会处在何种状态:
  
 注意:单向箭头表示不可逆
1.0 新建态到就绪态
- 概念:1. 新建态:一个线程被创建出来时候所处的状态 ;2. 就绪态:线程调用start()方法后,便处于可以被操作系统调度的状态,即就绪态。该状态可以由三处转化而来,新建态执行了start、线程阻塞结束、锁池等待队列中的线程获得了锁
        Thread t1 = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i < 10; i++) {
                            System.out.println("hello : " + i);
                        }
                    }
                }
        );
        // t1执行start()之后,处于就绪态,操作系统此时可以分配时间片给该线程,让该线程执行run方法体中的内容
        t1.start();
- 该状态对应状态图中的第0步,比较简单,不再赘述
1.1 就绪态到运行态
- 概念:运行态:表示当前线程被操作系统调度,分配了时间片,执行线程中的run方法时的状态。运行态只可以由就绪态的线程转化而来,如果多个线程都处在就绪态,就等待操作系统分配
public static void main(String[] args) {
        // 线程1
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println("t1 : running");
            }
        });
        t1.start();
        // 线程2
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println("t2 : running");
            }
        });
        t2.start();
    }
- 注:可以看到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()方法不能用来控制线程的执行顺序
    public static void main(String[] args) {
        // 线程1
        Thread t1 = new Thread(() -> {
            Thread.yield();
            for (int i = 0; i < 10; i++) {
                System.out.println("t1 : running " + i);
            }
        });
        t1.start();
        // 线程2
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println("t2 : running " + i);
            }
        });
        t2.start();
    }
- 注意:这个程序我故意把线程让步yield()方法写在线程体刚运行的时候,也就是说,每次操作系统分配给t1线程时间片时候,t1都会让步。但这次的让步不代表t1接下来的方法不会执行,也就是我让步之后,大家再一起抢,t1又抢到了时间片,那么t1本次时间片内便执行接下来的方法,等时间片结束,再次分配t1时间片,t1还会让,再接着抢,抢到和抢不到都有可能。
1.3 运行态到阻塞态
- 概念:阻塞态表示当前线程被由于某种原因,被挂起,也就是被阻塞,正在运行的线程被阻塞后,即使结束阻塞状态也回不去运行态,只能回到就绪态,等待os分配cpu资源去调度
1.3.1 Thread.sleep()
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
                    for (int i = 0; i < 10; i++) {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("hello : " + i);
                    }
                }
        );
        // t1执行start()之后,处于就绪态,操作系统此时可以分配时间片给该线程
        t1.start();
    }
- 注意:让当前线程睡眠,该线程被阻塞,睡眠时间结束,该线程接着运行
1.3.2 t2.join()
- 当在t1中调用t2.join()。那么t1会阻塞,一直等待t2执行完毕,才结束阻塞回到就绪态
- 直接看代码:这里我把t1和t2抽出来当做全局静态变量
public class TestThread {
    static Thread t1;
    static Thread t2;
    public static void main(String[] args) {
        // 线程1
        t1 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                if(i == 50) {
                    try {
                        t2.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("t1 : running " + i);
            }
        });
        t1.start();
        // 线程2
        t2 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                System.out.println("t2 : running " + i);
            }
        });
        t2.start();
    }
}
- 解释:这个程序的运行结果是,首先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:
    public static void main(String[] args) {
        // 准备上锁的对象
        Object o = new Object();
        // 线程1
        Thread t1 = new Thread(() -> {
            synchronized (o) {
                for (int i = 0; i < 10; i++) {
                    try {
                        if(i == 5) {
                            // 当i=5的时候,让出对象锁,t1进入等待队列
                            // 如果没人通知,t1一直等待,程序不会结束
                            o.wait();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("t1 : running " + i);
                }
            }
        });
        t1.start();
        // 线程2
        Thread t2 = new Thread(() -> {
            synchronized (o) {
                for (int i = 0; i < 10; i++) {
                    System.out.println("t2 : running " + i);
                }
                // 这里t2得到锁,执行完线程方法之后一定要通知t1停止等待。
                // 不然t1结束不了,处在一直等待通知的状态
                o.notify();
            }
        });
        t2.start();
    }
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线程状态转移的更多相关文章
- Java线程状态Jstack线程状态BLOCKED/TIMED_WAITING/WAITING解释
		一.线程5种状态 新建状态(New) 新创建了一个线程对象. 就绪状态(Runnable) 线程对象创建后,其他线程调用了该对象的start()方法.该状态的线程位于可运行线程池中,变得可运行,等待获 ... 
- Java线程状态转换
		前言:对于Java线程状态方面的知识点,笔者总感觉朦朦胧胧,趁着最近整理资料,将Java线程状态方面的知识点总结归纳,以便加深记忆. 1.Java线程状态值 在Thread类源码中通过枚举为线程定义了 ... 
- Java线程状态及 wait、sleep、join、interrupt、yield等的区别
		Java中的线程状态(详见Java线程状态及转换-MarchOn): wait:Object类的实例方法,释放CPU执行权,进入等待状态,直到 被中断.被拥有该对象锁的线程唤醒(notify或not ... 
- 深入理解Java线程池:ScheduledThreadPoolExecutor
		介绍 自JDK1.5开始,JDK提供了ScheduledThreadPoolExecutor类来支持周期性任务的调度.在这之前的实现需要依靠Timer和TimerTask或者其它第三方工具来完成.但T ... 
- Java线程状态、线程start方法源码、多线程、Java线程池、如何停止一个线程
		下面将依次介绍: 1. 线程状态.Java线程状态和线程池状态 2. start方法源码 3. 什么是线程池? 4. 线程池的工作原理和使用线程池的好处 5. ThreadPoolExecutor中的 ... 
- 深入理解 Java 线程池
		一.简介 什么是线程池 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务. 为什么要用线程池 如果并发请求数量很多,但每个线程执行的时间很短,就会出现频繁的创建 ... 
- JVM探秘:jstack查看Java线程状态
		本系列笔记主要基于<深入理解Java虚拟机:JVM高级特性与最佳实践 第2版>,是这本书的读书笔记. jstack命令可以打印Java进程的各个线程堆栈跟踪信息,可以用来查看Java中各个 ... 
- 浅谈 Java线程状态转换及控制
		线程的状态(系统层面) 一个线程被创建后就进入了线程的生命周期.在线程的生命周期中,共包括新建(New).就绪(Runnable).运行(Running).阻塞(Blocked)和死亡(Dead)这五 ... 
- Java线程状态及切换
		Java线程状态及切换 一.什么是Java线程状态 在Java程序中,用于描述Java线程的六种状态: 新建(NEW):当前线程,刚刚新建出来,尚未启动. 运行(RUNNABLE):当前线程,处于竞争 ... 
随机推荐
- NBL小可爱纪念赛「 第一弹 」 游记(部分题解)
			比赛链接 洛谷:禁止含有侮辱性质的比赛 . ??? 反正我觉得,gyx挺危险的 不说废话. 首先,比赛经验,前几个小时不打,跟着刷榜. 一看 T1. 发现是道水题,直接切掉了. 然后看到了 T2. 感 ... 
- Spark作业提交至Yarn上执行的 一个异常
			(1)控制台Yarn(Cluster模式)打印的异常日志: client token: N/A diagnostics: Application application_1584359 ... 
- Manjaro更新后 搜狗拼音输入法突然无法正常使用
			之前Manjaro已经用了很久了,很多该配置的都已经配置好了,但是搜狗拼音在系统更新后突然无法使用 1检查 如下依赖 2.检查配置文件 3.发现一切配置没问题,此时输入 sogou-qimpanel ... 
- SQL server 2012安装教程
			转自:https://blog.csdn.net/u013162035/article/details/78567389 注]博主使用的是SQL Server2012 其他版本的安装类似.[第一步]下 ... 
- 在linux虚拟机上安装docker并安装mysql
			步骤 1.检查内核版本,必须是3.10及以上 uname -r 2.安装docker yum install docker 3.输入y确认安装 4.启动docker systemctl start d ... 
- win10 安装redis相关问题。
			最近需要在win10安装redis,但是redis的msi文件总是报这个错误: Redis on Windows Setup Wizard ended prematurely 都说是.NET fram ... 
- 【swagger】C# 中 swagger 的使用及避坑
			@ 目录 1 安装 2 修改名称和版本号 3 显示说明 4 显示控制器注释及汉化 5 路由相同,查询参数不同的方法 6 忽略 Model 中的某些字段 7 传递 header 8 出错时的 HTTP ... 
- .Net微服务实践(三):Ocelot配置路由和请求聚合
			目录 配置 路由 基本配置 占位符 万能模板 优先级 查询参数 请求聚合 默认聚合 自定义聚合 最后 在上篇.Net微服务实践(二):Ocelot介绍和快速开始中我们介绍了Ocelot,创建了一个Oc ... 
- Oracle创建函数例子
			编写一个函数计算学生某一门课程在班级内的排名. 表结构如下: create or replace function fun_score_rank( p_in_stuid in number,--学号 ... 
- 逍遥云天 H5外部浏览器直接调起微信——通过url协议 weixin:// 判断是否安装微信及启动微信
			h5分享到微信,h5使用微信支付这些功能,都需要先判断是否安装微信客户端,如果已安装就启动微信,如果没有安装微信,就提示用户前去安装. 我们可以通过访问微信提供的URL协议(weixin://)来实现 ... 
