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

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的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单

《Springboot的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单》:本文主要介绍Springboot的ThreadPoolTaskScheduler线... 目录ThreadPoolTaskScheduler线程池实现15分钟不操作自动取消订单概要1,创建订单后

C语言线程池的常见实现方式详解

《C语言线程池的常见实现方式详解》本文介绍了如何使用C语言实现一个基本的线程池,线程池的实现包括工作线程、任务队列、任务调度、线程池的初始化、任务添加、销毁等步骤,感兴趣的朋友跟随小编一起看看吧... 目录1. 线程池的基本结构2. 线程池的实现步骤3. 线程池的核心数据结构4. 线程池的详细实现4.1 初

Java子线程无法获取Attributes的解决方法(最新推荐)

《Java子线程无法获取Attributes的解决方法(最新推荐)》在Java多线程编程中,子线程无法直接获取主线程设置的Attributes是一个常见问题,本文探讨了这一问题的原因,并提供了两种解决... 目录一、问题原因二、解决方案1. 直接传递数据2. 使用ThreadLocal(适用于线程独立数据)

豆包 MarsCode 不允许你还没有女朋友

在这个喧嚣的世界里,爱意需要被温柔地唤醒。为心爱的她制作每日一句小工具,就像是一场永不落幕的浪漫仪式,每天都在她的心田播撒爱的种子,让她的每一天都充满甜蜜与期待。 背景 在这个瞬息万变的时代,我们都在寻找那些能让我们慢下来,感受生活美好的瞬间。为了让这份浪漫持久而深刻,我们决定为女朋友定制一个每日一句小工具。这个工具会在她意想不到的时刻,为她呈现一句充满爱意的话语,让她的每一天都充满惊喜和感动

Maven(插件配置和生命周期的绑定)

1.这篇文章很好,介绍的maven插件的。 2.maven的source插件为例,可以把源代码打成包。 Goals Overview就可以查看该插件下面所有的目标。 这里我们要使用的是source:jar-no-fork。 3.查看source插件的example,然后配置到riil-collect.xml中。  <build>   <plugins>    <pl

线程的四种操作

所属专栏:Java学习        1. 线程的开启 start和run的区别: run:描述了线程要执行的任务,也可以称为线程的入口 start:调用系统函数,真正的在系统内核中创建线程(创建PCB,加入到链表中),此处的start会根据不同的系统,分别调用不同的api,创建好之后的线程,再单独去执行run(所以说,start的本质是调用系统api,系统的api

java线程深度解析(六)——线程池技术

http://blog.csdn.net/Daybreak1209/article/details/51382604 一种最为简单的线程创建和回收的方法: [html]  view plain copy new Thread(new Runnable(){                @Override               public voi

java线程深度解析(五)——并发模型(生产者-消费者)

http://blog.csdn.net/Daybreak1209/article/details/51378055 三、生产者-消费者模式     在经典的多线程模式中,生产者-消费者为多线程间协作提供了良好的解决方案。基本原理是两类线程,即若干个生产者和若干个消费者,生产者负责提交用户请求任务(到内存缓冲区),消费者线程负责处理任务(从内存缓冲区中取任务进行处理),两类线程之

java线程深度解析(四)——并发模型(Master-Worker)

http://blog.csdn.net/daybreak1209/article/details/51372929 二、Master-worker ——分而治之      Master-worker常用的并行模式之一,核心思想是由两个进程协作工作,master负责接收和分配任务,worker负责处理任务,并把处理结果返回给Master进程,由Master进行汇总,返回给客

java线程深度解析(二)——线程互斥技术与线程间通信

http://blog.csdn.net/daybreak1209/article/details/51307679      在java多线程——线程同步问题中,对于多线程下程序启动时出现的线程安全问题的背景和初步解决方案已经有了详细的介绍。本文将再度深入解析对线程代码块和方法的同步控制和多线程间通信的实例。 一、再现多线程下安全问题 先看开启两条线程,分别按序打印字符串的