前言

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

转载注明出处原文地址: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线程状态转移的更多相关文章

  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. HTTP、TCP、IP协议面试题

    HTTP.TCP.IP协议基本定义 HTTP: (HyperText Transport Protocol)是超文本传输协议的缩写,它用于传送WWW方式的数据,关于HTTP协议的详细内容请参考RFC2 ...

  2. POJ - 1276 二进制优化多重背包为01背包

    题意:直接说数据,735是目标值,然后3是后面有三种钱币,四张125的,六张五块的和三张350的. 思路:能够轻易的看出这是一个多重背包问题,735是背包的容量,那些钱币是物品,而且有一定的数量,是多 ...

  3. iOS 项目发布

    一.Apple开发者账号 1.1 开发者账号类型 个人级 公司级 企业级 公司和企业的可多人协作. 在苹果的开发者平台登录后,可在 People 界面邀请其他人员协作开发,邀请的人需要注册一个 app ...

  4. 记录---java中jsp页面引入jquery路径的问题

    今天在jsp页面中引入jquery的时候因为路径不对总是报404,网上的方法找到几种试了试但是最后结果还是不生效,遂想起原先的项目中有引入外部jquery的例子,所以立马看了看,发现当时的项目中是用$ ...

  5. SQL实战(三)

    一. 查找所有员工自入职以来的薪水涨幅情况,给出员工编号emp_noy以及其对应的薪水涨幅growth,并按照growth进行升序CREATE TABLE `employees` (`emp_no` ...

  6. 1000行MySQL学习笔记,不怕你不会,就怕你不学!

    Windows服务 -- 启动MySQL net start mysql-- 创建Windows服务 sc create mysql binPath= mysqld_bin_path(注意:等号与值之

  7. 关于MySQL Notifiter的简单使用

    MySQL Notifiter是MySQL 数据库的辅助工具. 1.打开MySQL Notifiter Command Line Client,输入密码:123456(这是我自己的) 2.创建一个名为 ...

  8. vue-cli3引入svg图标全过程以及遇到的坑

    https://blog.csdn.net/weixin_41229588/article/details/101159755 一.配置 1.安装依赖: npm install svg-sprite- ...

  9. mybatis源码配置文件解析之一:解析properties标签

    mybatis作为日常开发的常用ORM框架,在开发中起着很重要的作用,了解其源码对日常的开发有很大的帮助.源码版本为:3-3.4.x,可执行到github进行下载. 从这篇文章开始逐一分析mybati ...

  10. redis 安装and对外开放端口

    第一步: $ cd /usr/local/src $ wget http://download.redis.io/releases/redis-5.0.4.tar.gz $ tar xzf redis ...