面试必问 | 一个线程从创建到消亡要经历哪些阶段?

本文涉及的产品
云原生大数据计算服务 MaxCompute,5000CU*H 100GB 3个月
云原生大数据计算服务MaxCompute,500CU*H 100GB 3个月
简介: 在【精通高并发系列】中的《高并发之——线程与多线程》一文中,我们简单介绍了线程的生命周期和线程的几个重要状态,并以代码的形式实现了线程是如何进入各个状态的。


大家好,我是冰河~~

在【精通高并发系列】中的《高并发之——线程与多线程》一文中,我们简单介绍了线程的生命周期和线程的几个重要状态,并以代码的形式实现了线程是如何进入各个状态的。


今天,我们就结合 操作系统线程和编程语言线程 再次深入探讨线程的生命周期问题,线程的生命周期其实没有我们想象的那么简单!!

理解线程的生命周期本质上理解了生命周期中各个节点的状态转换机制就可以了。接下来,我们分别就 通用线程生命周期和Java语言的线程生命周期 分别进行详细说明。


通用的线程生命周期


通用的线程生命周期总体上可以分为五个状态:初始状态、可运行状态、运行状态、休眠状态和终止状态。

我们可以简单的使用下图来表示这五种状态。

图片.pngimage.gif


初始状态

线程已经被创建,但是不允许分配CPU执行。需要注意的是:这个状态属于编程语言特有,这里指的线程已经被创建,仅仅指在编程语言中被创建,在操作系统中,并没有创建真正的线程。


可运行状态

线程可以分配CPU执行。此时,操作系统中的线程被成功创建,可以分配CPU执行。


运行状态

当操作系统中存在空闲的CPU,操作系统会将这个空闲的CPU分配给一个处于可运行状态的线程,被分配到CPU的线程的状态就转换成了运行状态


休眠状态

运行状态的线程调用一个阻塞的API(例如,以阻塞的方式读文件)或者等待某个事件(例如,等待某个条件变量等),线程的状态就会转换到休眠状态。此时线程会释放CPU资源,休眠状态的线程没有机会获得CPU的使用权。一旦等待的条件出现,线程就会从休眠状态转换到可运行状态。


终止状态

线程执行完毕或者出现异常就会进入终止状态,终止状态的线程不会切换到其他任何状态,这也意味着线程的生命周期结束了。

以上就是通用的线程生命周期,下面,我们再看对比看下Java语言中的线程生命周期。


Java中的线程生命周期


Java中的线程生命周期总共可以分为六种状态:初始化状态(NEW)、可运行/运行状态(RUNNABLE)、阻塞状态(BLOCKED)、无时限等待状态(WAITING)、有时限等待状态(TIMED_WAITING)、终止状态(TERMINATED)。

需要大家重点理解的是:虽然Java语言中线程的状态比较多,但是,其实在操作系统层面,Java线程中的阻塞状态(BLOCKED)、无时限等待状态(WAITING)、有时限等待状态(TIMED_WAITING)都是一种状态,即通用线程生命周期中的休眠状态。也就是说,只要Java中的线程处于这三种状态时,那么,这个线程就没有CPU的使用权。

理解了这些之后,我们就可以使用下面的图来简单的表示Java中线程的生命周期。

image.gif图片.png


我们也可以这样理解阻塞状态(BLOCKED)、无时限等待状态(WAITING)、有时限等待状态(TIMED_WAITING),它们是导致线程休眠的三种原因!

接下来,我们就看看Java线程中的状态是如何转化的。


RUNNABLE与BLOCKED的状态转换


只有一种场景会触发这种转换,就是线程等待synchronized隐式锁。synchronized修饰的方法、代码块同一时刻只允许一个线程执行,其他的线程则需要等待。


此时,等待的线程就会从RUNNABLE状态转换到BLOCKED状态。当等待的线程获得synchronized隐式锁时,就又会从BLOCKED状态转换到RUNNABLE状态。


这里,需要大家注意:线程调用阻塞API时,在操作系统层面,线程会转换到休眠状态。但是在JVM中,Java线程的状态不会发生变化,也就是说,Java线程的状态仍然是RUNNABLE状态。


JVM并不关心操作系统调度相关的状态,在JVM角度来看,等待CPU使用权(操作系统中的线程处于可执行状态)和等待IO操作(操作系统中的线程处于休眠状态)没有区别,都是在等待某个资源,所以,将其都归入了RUNNABLE状态。


我们平时所说的Java在调用阻塞API时,线程会阻塞,指的是操作系统线程的状态,并不是Java线程的状态。


RUNNABLE与WAITING状态转换


线程从RUNNABLE状态转换成WAITING状态总体上有三种场景。


场景一

获得synchronized隐式锁的线程,调用无参的Object.wait()方法。此时的线程会从RUNNABLE状态转换成WAITING状态。


场景二

调用无参数的Thread.join()方法。其中join()方法是一种线程的同步方法。例如,在threadA线程中调用threadB线程的join()方法,则threadA线程会等待threadB线程执行完。

而threadA线程在等待threadB线程执行的过程中,其状态会从RUNNABLE转换到WAITING。当threadB执行完毕,threadA线程的状态则会从WAITING状态转换成RUNNABLE状态。


场景三

调用LockSupport.park()方法,当前线程会阻塞,线程的状态会从RUNNABLE转换成WAITING。

调用LockSupport.unpark(Thread thread)可唤醒目标线程,目标线程的状态又会从WAITING状态转换到RUNNABLE。


RUNNABLE与TIMED_WAITING状态转换


总体上可以分为五种场景。


场景一

调用带超时参数的Thread.sleep(long millis)方法;


场景二

获得synchronized隐式锁的线程,调用带超时参数的Object.wait(long timeout)参数;


场景三

调用带超时参数的Thread.join(long millis)方法;


场景四

调用带超时参数的LockSupport.parkNanos(Object blocker, long deadline)方法;


场景五

调用带超时参数的LockSuppor.parkUntil(long deadline)方法。


从NEW到RUNNABLE状态


Java刚创建出来的Thread对象就是NEW状态,创建Thread对象主要有两种方法,一种是继承Thread对象,重写run()方法;另一种是实现Runnable接口,重写run()方法。

注意:这里说的是创建Thread对象的方法,而不是创建线程的方法,创建线程的方法包含创建Thread对象的方法。


继承Thread对象

public class ChildThread extends Thread{
    @Override
    public void run(){
        //线程中需要执行的逻辑
    }
}
//创建线程对象
ChildThread childThread = new ChildThread();


实现Runnable接口

public class ChildRunnable implements Runnable{
    @Override
    public void run(){
        //线程中需要执行的逻辑
    }
}
//创建线程对象
Thread childThread = new Thread(new ChildRunnable());


处于NEW状态的线程不会被操作系统调度,因此也就不会执行。Java中的线程要执行,就需要转换到RUNNABLE状态。从NEW状态转换到RUNNABLE状态,只需要调用线程对象的start()方法即可。

//创建线程对象
Thread childThread = new Thread(new ChildRunnable());
//调用start()方法使线程从NEW状态转换到RUNNABLE状态
childThread.start();


RUNNABLE到TERMINATED状态


线程执行完run()方法后,或者执行run()方法的时候抛出异常,都会终止,此时为TERMINATED状态。如果我们需要中断run()方法,可以调用interrupt()方法。


写在最后


如果觉得文章对你有点帮助,请微信搜索并关注「 冰河技术 」微信公众号,跟冰河学习高并发编程技术。

添加冰河微信 hacker_binghe 」备注进大厂获取一线互联网大厂内部资料。

好了,今天就到这儿吧,我是冰河,我们下期见~~

---END---

后台回复 “并发编程” 领取冰河原创的全网累计下载超50W+的《深入理解高并发编程》电子书。回复 “渗透笔记” 领取冰河原创的全网首个开源的以实战案例为背景的《冰河的渗透实战笔记》电子书。回复 “PDF” 领取冰河整理的其他8本超硬核PDF电子书,海量面试资料和简历模板。冰河从一名普通程序员,短短几年时间,一路进阶成长为互联网高级技术专家,一直致力于分布式系统架构、微服务、分布式数据库、分布式事务与大数据技术的研究。在高并发、高可用、高可扩展性、高可维护性和大数据等领域拥有丰富的架构经验。对Hadoop,Storm,Spark,Flink等大数据框架源码进行过深度分析,并具有丰富的实战经验。出版过三本畅销书《深入理解分布式事务:原理与实战》、《海量数据处理与大数据技术实战》、《MySQL技术大全:开发、优化与运维实战》。写了一本《深入理解高并发编程》电子书全网累计下载50W+,发布了一本全网首个开源的以实战案例为背景的《冰河的渗透实战笔记》电子书,全网五星好评。写的文章多次被微信公众号官方推荐。

相关实践学习
基于MaxCompute的热门话题分析
本实验围绕社交用户发布的文章做了详尽的分析,通过分析能得到用户群体年龄分布,性别分布,地理位置分布,以及热门话题的热度。
SaaS 模式云数据仓库必修课
本课程由阿里云开发者社区和阿里云大数据团队共同出品,是SaaS模式云原生数据仓库领导者MaxCompute核心课程。本课程由阿里云资深产品和技术专家们从概念到方法,从场景到实践,体系化的将阿里巴巴飞天大数据平台10多年的经过验证的方法与实践深入浅出的讲给开发者们。帮助大数据开发者快速了解并掌握SaaS模式的云原生的数据仓库,助力开发者学习了解先进的技术栈,并能在实际业务中敏捷的进行大数据分析,赋能企业业务。 通过本课程可以了解SaaS模式云原生数据仓库领导者MaxCompute核心功能及典型适用场景,可应用MaxCompute实现数仓搭建,快速进行大数据分析。适合大数据工程师、大数据分析师 大量数据需要处理、存储和管理,需要搭建数据仓库?学它! 没有足够人员和经验来运维大数据平台,不想自建IDC买机器,需要免运维的大数据平台?会SQL就等于会大数据?学它! 想知道大数据用得对不对,想用更少的钱得到持续演进的数仓能力?获得极致弹性的计算资源和更好的性能,以及持续保护数据安全的生产环境?学它! 想要获得灵活的分析能力,快速洞察数据规律特征?想要兼得数据湖的灵活性与数据仓库的成长性?学它! 出品人:阿里云大数据产品及研发团队专家 产品 MaxCompute 官网 https://www.aliyun.com/product/odps 
相关文章
|
2月前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
2月前
|
消息中间件 前端开发 NoSQL
面试官:线程池遇到未处理的异常会崩溃吗?
面试官:线程池遇到未处理的异常会崩溃吗?
74 3
面试官:线程池遇到未处理的异常会崩溃吗?
|
2月前
|
消息中间件 存储 前端开发
面试官:说说停止线程池的执行流程?
面试官:说说停止线程池的执行流程?
51 2
面试官:说说停止线程池的执行流程?
|
2月前
|
消息中间件 前端开发 NoSQL
面试官:如何实现线程池任务编排?
面试官:如何实现线程池任务编排?
33 1
面试官:如何实现线程池任务编排?
|
3月前
|
Java
【多线程面试题二十五】、说说你对AQS的理解
这篇文章阐述了对Java中的AbstractQueuedSynchronizer(AQS)的理解,AQS是一个用于构建锁和其他同步组件的框架,它通过维护同步状态和FIFO等待队列,以及线程的阻塞与唤醒机制,来实现同步器的高效管理,并且可以通过实现特定的方法来自定义同步组件的行为。
【多线程面试题二十五】、说说你对AQS的理解
|
3月前
|
Java
【多线程面试题十六】、谈谈ReentrantLock的实现原理
这篇文章解释了`ReentrantLock`的实现原理,它基于Java中的`AbstractQueuedSynchronizer`(AQS)构建,通过重写AQS的`tryAcquire`和`tryRelease`方法来实现锁的获取与释放,并详细描述了AQS内部的同步队列和条件队列以及独占模式的工作原理。
【多线程面试题十六】、谈谈ReentrantLock的实现原理
|
3月前
|
消息中间件 缓存 算法
Java多线程面试题总结(上)
进程和线程是操作系统管理程序执行的基本单位,二者有明显区别: 1. **定义与基本单位**:进程是资源分配的基本单位,拥有独立的内存空间;线程是调度和执行的基本单位,共享所属进程的资源。 2. **独立性与资源共享**:进程间相互独立,通信需显式机制;线程共享进程资源,通信更直接快捷。 3. **管理与调度**:进程管理复杂,线程管理更灵活。 4. **并发与并行**:进程并发执行,提高资源利用率;线程不仅并发还能并行执行,提升执行效率。 5. **健壮性**:进程更健壮,一个进程崩溃不影响其他进程;线程崩溃可能导致整个进程崩溃。
47 2
|
3月前
|
存储 安全 容器
【多线程面试题二十一】、 分段锁是怎么实现的?
这篇文章解释了分段锁的概念和实现方式,通过将数据分成多个段并在每段数据上使用独立锁,从而降低锁竞争,提高并发访问效率,举例说明了`ConcurrentHashMap`如何使用分段锁技术来实现高并发和线程安全。
【多线程面试题二十一】、 分段锁是怎么实现的?
|
3月前
|
安全 Java
【多线程面试题十九】、 公平锁与非公平锁是怎么实现的?
这篇文章解释了Java中`ReentrantLock`的公平锁和非公平锁的实现原理,其中公平锁通过检查等待队列严格按顺序获取锁,而非公平锁允许新线程有更高机会立即获取锁,两者都依赖于`AbstractQueuedSynchronizer`(AQS)和`volatile`关键字以及CAS技术来确保线程安全和锁的正确同步。
【多线程面试题十九】、 公平锁与非公平锁是怎么实现的?
|
3月前
|
存储 缓存 安全
Java多线程面试题总结(中)
Java内存模型(JMM)定义了程序中所有变量的访问规则与范围,确保多线程环境下的数据一致性。JMM包含主内存与工作内存的概念,通过8种操作管理两者间的交互,确保原子性、可见性和有序性。`synchronized`和`volatile`关键字提供同步机制,前者确保互斥访问,后者保证变量更新的可见性。多线程操作涉及不同状态,如新建(NEW)、可运行(RUNNABLE)等,并可通过中断、等待和通知等机制协调线程活动。`volatile`虽不确保线程安全,但能确保变量更新对所有线程可见。
19 0