很遗憾,没有一篇文章能讲清楚线程的生命周期!

2023-12-21 11:32

本文主要是介绍很遗憾,没有一篇文章能讲清楚线程的生命周期!,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

(手机横屏看源码更方便)


注: java源码分析部分如无特殊说明均基于 java8 版本。

简介

大家都知道线程是有生命周期,但是彤哥可以认真负责地告诉你网上几乎没有一篇文章讲得是完全正确的。

常见的错误有:就绪状态、运行中状态(RUNNING)、死亡状态、中断状态、只有阻塞没有等待状态、流程图乱画等,最常见的错误就是说线程只有5种状态。

今天这篇文章会彻底讲清楚线程的生命周期,并分析synchronized锁、基于AQS的锁中线程状态变化的逻辑。

所以,对synchronized锁和AQS原理(源码)不了解的同学,请翻一下彤哥之前的文章先熟悉这两部分的内容,否则肯定记不住这里讲的线程生命周期。

问题

(1)线程的状态有哪些?

(2)synchronized锁各阶段线程处于什么状态?

(3)重入锁、条件锁各阶段线程处于什么状态?

先上源码

关于线程的生命周期,我们可以看一下java.lang.Thread.State这个类,它是线程的内部枚举类,定义了线程的各种状态,并且注释也很清晰。


public enum State {/*** 新建状态,线程还未开始*/NEW,/*** 可运行状态,正在运行或者在等待系统资源,比如CPU*/RUNNABLE,/*** 阻塞状态,在等待一个监视器锁(也就是我们常说的synchronized)* 或者在调用了Object.wait()方法且被notify()之后也会进入BLOCKED状态*/BLOCKED,/*** 等待状态,在调用了以下方法后进入此状态* 1. Object.wait()无超时的方法后且未被notify()前,如果被notify()了会进入BLOCKED状态* 2. Thread.join()无超时的方法后* 3. LockSupport.park()无超时的方法后*/WAITING,/*** 超时等待状态,在调用了以下方法后会进入超时等待状态* 1. Thread.sleep()方法后【本文由公从号“彤哥读源码”原创】* 2. Object.wait(timeout)方法后且未到超时时间前,如果达到超时了或被notify()了会进入BLOCKED状态* 3. Thread.join(timeout)方法后* 4. LockSupport.parkNanos(nanos)方法后* 5. LockSupport.parkUntil(deadline)方法后*/TIMED_WAITING,/*** 终止状态,线程已经执行完毕*/TERMINATED;
}

流程图

线程生命周期中各状态的注释完毕了,下面我们再来 看看各状态之间的流转:

怎么样?是不是很复杂?彤哥几乎把网上的资料都查了一遍,没有一篇文章把这个流程图完整画出来的,下面彤哥就来一一解释:

(1)为了方便讲解,我们把锁分成两大类,一类是synchronized锁,一类是基于AQS的锁(我们拿重入锁举例),也就是内部使用了LockSupport.park()/parkNanos()/parkUntil()几个方法的锁;

(2)不管是synchronized锁还是基于AQS的锁,内部都是分成两个队列,一个是同步队列(AQS的队列),一个是等待队列(Condition的队列);

(3)对于内部调用了object.wait()/wait(timeout)或者condition.await()/await(timeout)方法,线程都是先进入等待队列,被notify()/signal()或者超时后,才会进入同步队列;

(4)明确声明,BLOCKED状态只有线程处于synchronized的同步队列的时候才会有这个状态,其它任何情况都跟这个状态无关;

(5)对于synchronized,线程执行synchronized的时候,如果立即获得了锁(没有进入同步队列),线程处于RUNNABLE状态;

(6)对于synchronized,线程执行synchronized的时候,如果无法获得锁(直接进入同步队列),线程处于BLOCKED状态;

(5)对于synchronized内部,调用了object.wait()之后线程处于WAITING状态(进入等待队列);

(6)对于synchronized内部,调用了object.wait(timeout)之后线程处于TIMED_WAITING状态(进入等待队列);

(7)对于synchronized内部,调用了object.wait()之后且被notify()了,如果线程立即获得了锁(也就是没有进入同步队列),线程处于RUNNABLE状态;

(8)对于synchronized内部,调用了object.wait(timeout)之后且被notify()了,如果线程立即获得了锁(也就是没有进入同步队列),线程处于RUNNABLE状态;

(9)对于synchronized内部,调用了object.wait(timeout)之后且超时了,这时如果线程正好立即获得了锁(也就是没有进入同步队列),线程处于RUNNABLE状态;

(10)对于synchronized内部,调用了object.wait()之后且被notify()了,如果线程无法获得锁(也就是进入了同步队列),线程处于BLOCKED状态;

(11)对于synchronized内部,调用了object.wait(timeout)之后且被notify()了或者超时了,如果线程无法获得锁(也就是进入了同步队列),线程处于BLOCKED状态;

(12)对于重入锁,线程执行lock.lock()的时候,如果立即获得了锁(没有进入同步队列),线程处于RUNNABLE状态;

(13)对于重入锁,线程执行lock.lock()的时候,如果无法获得锁(直接进入同步队列),线程处于WAITING状态;

(14)对于重入锁内部,调用了condition.await()之后线程处于WAITING状态(进入等待队列);

(15)对于重入锁内部,调用了condition.await(timeout)之后线程处于TIMED_WAITING状态(进入等待队列);

(16)对于重入锁内部,调用了condition.await()之后且被signal()了,如果线程立即获得了锁(也就是没有进入同步队列),线程处于RUNNABLE状态;

(17)对于重入锁内部,调用了condition.await(timeout)之后且被signal()了,如果线程立即获得了锁(也就是没有进入同步队列),线程处于RUNNABLE状态;

(18)对于重入锁内部,调用了condition.await(timeout)之后且超时了,这时如果线程正好立即获得了锁(也就是没有进入同步队列),线程处于RUNNABLE状态;

(19)对于重入锁内部,调用了condition.await()之后且被signal()了,如果线程无法获得锁(也就是进入了同步队列),线程处于WAITING状态;

(20)对于重入锁内部,调用了condition.await(timeout)之后且被signal()了或者超时了,如果线程无法获得锁(也就是进入了同步队列),线程处于WAITING状态;

(21)对于重入锁,如果内部调用了condition.await()之后且被signal()之后依然无法获取锁的,其实经历了两次WAITING状态的切换,一次是在等待队列,一次是在同步队列;

(22)对于重入锁,如果内部调用了condition.await(timeout)之后且被signal()或超时了的,状态会有一个从TIMED_WAITING切换到WAITING的过程,也就是从等待队列进入到同步队列;

为了便于理解,彤哥这里每一条都分的比较细,麻烦耐心看完。

测试用例

看完上面的部分,你肯定想知道怎么去验证,下面彤哥就说说验证的方法,先给出测试用例。

public class ThreadLifeTest {public static void main(String[] args) throws IOException {Object object = new Object();ReentrantLock lock = new ReentrantLock();Condition condition = lock.newCondition();new Thread(()->{synchronized (object) {try {System.out.println("thread1 waiting");object.wait();
//                    object.wait(5000);System.out.println("thread1 after waiting");} catch (InterruptedException e) {e.printStackTrace();}}}, "Thread1").start();new Thread(()->{synchronized (object) {try {System.out.println("thread2 notify");// 打开或关闭这段注释,观察Thread1的状态
//                    object.notify();【本文由公从号“彤哥读源码”原创】// notify之后当前线程并不会释放锁,只是被notify的线程从等待队列进入同步队列// sleep也不会释放锁Thread.sleep(10000000);} catch (InterruptedException e) {e.printStackTrace();}}}, "Thread2").start();new Thread(()->{lock.lock();System.out.println("thread3 waiting");try {condition.await();
//                condition.await(200, (TimeUnit).SECONDS);System.out.println("thread3 after waiting");} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}, "Thread3").start();new Thread(()->{lock.lock();System.out.println("thread4");// 打开或关闭这段注释,观察Thread3的状态
//            condition.signal();【本文由公从号“彤哥读源码”原创】// signal之后当前线程并不会释放锁,只是被signal的线程从等待队列进入同步队列// sleep也不会释放锁try {Thread.sleep(1000000);} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}, "Thread4").start();}
}

打开或关闭上面注释部分的代码,使用IDEA的RUN模式运行代码,然后点击左边的一个摄像头按钮(jstack),查看各线程的状态。

注:不要使用DEBUG模式,DEBUG模式全都变成WAITING状态了,很神奇。

彩蛋

其实,本来这篇是准备写线程池的生命周期的,奈何线程的生命周期写了太多,等下一篇我们再来一起学习线程池的生命周期吧。


这篇关于很遗憾,没有一篇文章能讲清楚线程的生命周期!的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/519815

相关文章

创建springBoot模块没有目录结构的解决方案

《创建springBoot模块没有目录结构的解决方案》2023版IntelliJIDEA创建模块时可能出现目录结构识别错误,导致文件显示异常,解决方法为选择模块后点击确认,重新校准项目结构设置,确保源... 目录创建spChina编程ringBoot模块没有目录结构解决方案总结创建springBoot模块没有目录

SpringBoot实现虚拟线程的方案

《SpringBoot实现虚拟线程的方案》Java19引入虚拟线程,本文就来介绍一下SpringBoot实现虚拟线程的方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录什么是虚拟线程虚拟线程和普通线程的区别SpringBoot使用虚拟线程配置@Async性能对比H

在Java中实现线程之间的数据共享的几种方式总结

《在Java中实现线程之间的数据共享的几种方式总结》在Java中实现线程间数据共享是并发编程的核心需求,但需要谨慎处理同步问题以避免竞态条件,本文通过代码示例给大家介绍了几种主要实现方式及其最佳实践,... 目录1. 共享变量与同步机制2. 轻量级通信机制3. 线程安全容器4. 线程局部变量(ThreadL

Linux线程同步/互斥过程详解

《Linux线程同步/互斥过程详解》文章讲解多线程并发访问导致竞态条件,需通过互斥锁、原子操作和条件变量实现线程安全与同步,分析死锁条件及避免方法,并介绍RAII封装技术提升资源管理效率... 目录01. 资源共享问题1.1 多线程并发访问1.2 临界区与临界资源1.3 锁的引入02. 多线程案例2.1 为

SQL Server安装时候没有中文选项的解决方法

《SQLServer安装时候没有中文选项的解决方法》用户安装SQLServer时界面全英文,无中文选项,通过修改安装设置中的国家或地区为中文中国,重启安装程序后界面恢复中文,解决了问题,对SQLSe... 你是不是在安装SQL Server时候发现安装界面和别人不同,并且无论如何都没有中文选项?这个问题也

Java中的xxl-job调度器线程池工作机制

《Java中的xxl-job调度器线程池工作机制》xxl-job通过快慢线程池分离短时与长时任务,动态降级超时任务至慢池,结合异步触发和资源隔离机制,提升高频调度的性能与稳定性,支撑高并发场景下的可靠... 目录⚙️ 一、调度器线程池的核心设计 二、线程池的工作流程 三、线程池配置参数与优化 四、总结:线程

WinForm跨线程访问UI及UI卡死的解决方案

《WinForm跨线程访问UI及UI卡死的解决方案》在WinForm开发过程中,跨线程访问UI控件和界面卡死是常见的技术难题,由于Windows窗体应用程序的UI控件默认只能在主线程(UI线程)上操作... 目录前言正文案例1:直接线程操作(无UI访问)案例2:BeginInvoke访问UI(错误用法)案例

Spring Boot 结合 WxJava 实现文章上传微信公众号草稿箱与群发

《SpringBoot结合WxJava实现文章上传微信公众号草稿箱与群发》本文将详细介绍如何使用SpringBoot框架结合WxJava开发工具包,实现文章上传到微信公众号草稿箱以及群发功能,... 目录一、项目环境准备1.1 开发环境1.2 微信公众号准备二、Spring Boot 项目搭建2.1 创建

Linux线程之线程的创建、属性、回收、退出、取消方式

《Linux线程之线程的创建、属性、回收、退出、取消方式》文章总结了线程管理核心知识:线程号唯一、创建方式、属性设置(如分离状态与栈大小)、回收机制(join/detach)、退出方法(返回/pthr... 目录1. 线程号2. 线程的创建3. 线程属性4. 线程的回收5. 线程的退出6. 线程的取消7.

Linux下进程的CPU配置与线程绑定过程

《Linux下进程的CPU配置与线程绑定过程》本文介绍Linux系统中基于进程和线程的CPU配置方法,通过taskset命令和pthread库调整亲和力,将进程/线程绑定到特定CPU核心以优化资源分配... 目录1 基于进程的CPU配置1.1 对CPU亲和力的配置1.2 绑定进程到指定CPU核上运行2 基于