Java面试必考题之线程的生命周期,结合源码,透彻讲解!
写在开头
在前面的几篇博客里,我们学习了Java的多线程,包括线程的作用、创建方式、重要性等,那么今天我们就要正式踏入线程,去学习更加深层次的知识点了。
第一个需要学的就是线程的生命周期,也可以将之理解为线程的几种状态,以及互相之间的切换,这几乎是Java多线程的面试必考题,每一年都有大量的同学,因为这部分内容回答不够完美而错过高薪,今天我们结合源码,好好来聊一聊。
线程的生命周期
所谓线程的生命周期,就是从它诞生到消亡的整个过程,而不同的编程语言,对线程生命周期的封装是不同的,在Java中线程整个生命周期被分为了六种状态,我们下面就来一起学习一下。
线程的6种状态
对于Java中线程的状态划分,我们其实要从两个方面去看,一是JVM层面,这是我们程序运行的核心,另一层面是操作系统层面,这是我们JVM能够运行的核心。为了更直观的分析,build哥列了一个对比图:

在操作系统层面,对于RUNNABLE状态拆分为(READY、RUNNING),那为什么在JVM层面没有分这么细致呢?
这是因为啊,在当下时分多任务操作系统架构下,线程的驱动是通过获取CPU时间片,而每个时间片的间隔非常之短(10-20ms),这就意味着一个线程在cpu上运行一次的时间在0.01秒,随后CPU执行权就会发生切换,在如此高频的切换下,JVM就没必要去区分READY和RUNNING了。
在Java的源码中也可以看到,确实只分了6种状态:
【源码解析1】
// Thread.State 源码
public enum State {
//省略了每个枚举值上的注释
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
NEW(初始化状态)
我们通过new一个Thread对象,进行了初始化工作,这时的线程还没有被启动。
【代码示例1】
public class Test {
public static void main(String[] args) {
//lambda 表达式
Thread thread = new Thread(() -> {});
System.out.println(thread.getState());
}
}
//执行结果:NEW
我们通过thread.getState()方法去获得当前线程所处在的状态,此时输出为NEW。
RUNNABLE(可运行状态)
对于这种状态的描述,我们来看一下Thread源码中如何说的:
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
大致意思是线程处于RUNNABLE状态下,代表它可能正处于运行状态,或者正在等待CPU资源的分配。
那么我们怎样从NEW状态变为RUNNABLE呢?答案很简单,我们只需要调用start()方法即可!
【代码示例2】
public class Test {
public static void main(String[] args) {
//lambda 表达式
Thread thread = new Thread(() -> {});
thread.start();
System.out.println(thread.getState());
}
}
//执行结果:RUNNABLE
BLOCKED(阻塞状态)
当线程线程进入 synchronized 方法/块或者调用 wait 后(被 notify)重新进入 synchronized 方法/块,但是锁被其它线程占有,这个时候线程就会进入 BLOCKED(阻塞) 状态。这时候只有等到锁被另外一个线程释放,重新获取锁后,阻塞状态解除!
WAITING(无限时等待)
当通过代码将线程转为WAITING状态后,这种状态不会自动切换为其他状态,是一种无限时状态,直到整个线程接收到了外界通知,去唤醒它,才会从WAITING转为uRUNNABLE。
调用下面这 3 个方法会使线程进入等待状态:
Object.wait():使当前线程处于等待状态直到另一个线程唤醒它;Thread.join():等待线程执行完毕,底层调用的是 Object 的 wait 方法;LockSupport.park():除非获得调用许可,否则禁用当前线程进行线程调度。
TIMED_WAITING(有限时等待)
与WAITING相比,TIMED_WAITING是一种有限时的状态,可以通过设置等待时间,没有外界干扰的情况下,达到指定等待时间后,自动终止等待状态,转为RUNNABLE状态。
调用如下方法会使线程进入超时等待状态:
Thread.sleep(long millis):使当前线程睡眠指定时间;Object.wait(long timeout):线程休眠指定时间,等待期间可以通过notify()/notifyAll()唤醒;Thread.join(long millis):等待当前线程最多执行 millis 毫秒,如果 millis 为 0,则会一直执行;LockSupport.parkNanos(long nanos): 除非获得调用许可,否则禁用当前线程进行线程调度指定时间;LockSupport.parkUntil(long deadline):同上,也是禁止线程进行调度指定时间;
TERMINATED(终止状态)
线程正常执行结束,或者异常终止,会转变到 TERMINATED 状态。
线程状态的切换
上面的6种状态随着程序的运行,代码(方法)的执行,上下文的切换,也伴随着状态的转变。

NEW 到 RUNNABLE 状态
这一种转变比较好理解,我们通过new,初始化一个Thread对象后,这时就是处于线程的NEW状态,此时线程是不会获取CPU时间片调度执行的,只有在调用了start()方法后,线程彻底创建完成,进入RUNNABLE状态,等待操作系统调度执行!这种状态是NEW -> RUNNABLE的单向转变。
RUNNABLE 与 BLOCKED 的状态转变
synchronized 修饰的方法、代码块同一时刻只允许一个线程执行,其他线程只能等待,等待的线程会从 RUNNABLE 转变到 BLOCKED 状态,当等待的线程获得 synchronized 隐式锁时,就又会从 BLOCKED 转变到 RUNNABLE 状态。我们通过一段代码示例看一下:
【代码示例3】
public class Test {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
testMethod();
});
Thread thread2 = new Thread(() -> {
testMethod();
});
thread1.start();
thread2.start();
System.out.println(thread1.getName()+":"+thread1.getState());
System.out.println(thread2.getName()+":"+thread2.getState());
}
// 同步方法争夺锁
private static synchronized void testMethod() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出:
Thread-0:RUNNABLE
Thread-1:BLOCKED
代码中在主线程中创建了2个线程,线程中都调用了同步方法,随后去启动线程,因为CPU的执行效率较高,还没阻塞已经完成的打印,所以大部分时间里会输出两线程均为RUNNABLE状态;
当CPU效率稍低时,就会呈现上述结果,thread1启动后进入RUNNABLE状态,并且获得了同步方法,这是thread2启动后,调用的同步方法锁已经被占用,它作为等待的线程会从 RUNNABLE 转变到 BLOCKED 状态,待到thread1同步方法执行完毕,释放synchronized锁后,thread2获得锁,从BLOCKED转为RUNNABLE状态。
RUNNABLE 与 WAITING 的状态转变
1、获得 synchronized 隐式锁的线程,调用无参数的 Object.wait() 方法,状态会从 RUNNABLE 转变到 WAITING;调用 Object.notify()、Object.notifyAll() 方法,线程可能从 WAITING 转变到 RUNNABLE 状态。
2、调用无参数的 Thread.join() 方法。join() 是一种线程同步方法,如有一线程对象 Thread t,当调用 t.join() 的时候,执行代码的线程的状态会从 RUNNABLE 转变到 WAITING,等待 thread t 执行完。当线程 t 执行完,等待它的线程会从 WAITING 状态转变到 RUNNABLE 状态。
3、调用 LockSupport.park() 方法,线程的状态会从 RUNNABLE 转变到 WAITING;调用 LockSupport.unpark(Thread thread) 可唤醒目标线程,目标线程的状态又会从 WAITING 转变为 RUNNABLE 状态。
RUNNABLE 与 TIMED_WAITING 的状态转变
这种与上面的很相似,只是在方法调用和参数上有细微差别,因为,TIMED_WAITING 和 WAITING 状态的区别,仅仅是调用的是超时参数的方法。
转变方法在上文中已经提到了,这里以sleep(time)为例,写一个测试案例:
【代码示例4】
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
testMethod();
});
Thread thread2 = new Thread(() -> {
// testMethod();
});
thread1.start();
Thread.sleep(1000L);
thread2.start();
System.out.println(thread1.getName()+":"+thread1.getState());
System.out.println(thread2.getName()+":"+thread2.getState());
}
// 同步方法争夺锁
private static synchronized void testMethod() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出:
Thread-0:TIMED_WAITING
Thread-1:TERMINATED
这里面我们启动threa1后,让主线程休眠了1秒,这时thread1获得同步方法后,方法内部执行了休眠2秒的操作,因此它处于TIMED_WAITING状态,而thread2正常运行结束,状态处于TERMINATED(这个案例同样可以印证下面RUNNABLE到TERMINATED的转变)。
RUNNABLE 到 TERMINATED 状态
转变为TERMINATED状态,表明这个线程已经执行完毕,通常用如下几种情况:
- 线程执行完 run() 方法后,会自动转变到 TERMINATED 状态;
- 执行 run() 方法时异常抛出,也会导致线程终止;
- Thread类的 stop() 方法已经不建议使用。
总结
今天关于线程的6种状态就讲到这里啦,这是个重点知识点,希望大家能够铭记于心呀!
结尾彩蛋
如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏呀。原创不易,转载请联系Build哥!

如果您想与Build哥的关系更近一步,还可以关注“JavaBuild888”,在这里除了看到《Java成长计划》系列博文,还有提升工作效率的小笔记、读书心得、大厂面经、人生感悟等等,欢迎您的加入!

Java面试必考题之线程的生命周期,结合源码,透彻讲解!的更多相关文章
- Java多线程(五)线程的生命周期
点我跳过黑哥的卑鄙广告行为,进入正文. Java多线程系列更新中~ 正式篇: Java多线程(一) 什么是线程 Java多线程(二)关于多线程的CPU密集型和IO密集型这件事 Java多线程(三)如何 ...
- drf复习(一)--原生djangoCBV请求生命周期源码分析、drf自定义配置文件、drf请求生命周期dispatch源码分析
admin后台注册model 一.原生djangoCBV请求生命周期源码分析 原生view的源码路径(django/views/generic/base.py) 1.从urls.py中as_view ...
- Java 多线程(三) 线程的生命周期及优先级
线程的生命周期 线程的生命周期:一个线程从创建到消亡的过程. 如下图,表示线程生命周期中的各个状态: 线程的生命周期可以分为四个状态: 1.创建状态: 当用new操作符创建一个新的线程对象时,该线程处 ...
- Java 多线程(三)—— 线程的生命周期及方法
这篇博客介绍线程的生命周期. 线程是一个动态执行的过程,它也有从创建到死亡的过程. 线程的几种状态 在 Thread 类中,有一个枚举内部类: 上面的信息以图片表示如下: 第一张图: 第二张图:把等待 ...
- Java面试必问之线程池的创建使用、线程池的核心参数、线程池的底层工作原理
一.前言 大家在面试过程中,必不可少的问题是线程池,小编也是在面试中被问啥傻了,JUC就了解的不多.加上做系统时,很少遇到,自己也是一知半解,最近看了尚硅谷阳哥的课,恍然大悟,特写此文章记录一下!如果 ...
- java多线程(二)线程的生命周期
1. 线程生命周期 新建(New).就绪(Runnable).运行(Running).阻塞(Blocked)和死亡(Dead)5种状态. 1.1. 新建 l new关键字创建了一个线程之后,该线 ...
- java多线程(二)-线程的生命周期及线程间通信
一.摘要 当我们将线程创建并start时候,它不会一直占据着cpu执行,而是多个线程间会去执行着这个cpu,此时这些线程就会在多个状态之间进行着切换. 在线程的生命周期中,它会有5种状态,分别为 ...
- Java 多线程(五)—— 线程池基础 之 FutureTask源码解析
FutureTask是一个支持取消行为的异步任务执行器.该类实现了Future接口的方法. 如: 取消任务执行 查询任务是否执行完成 获取任务执行结果(”get“任务必须得执行完成才能获取结果,否则会 ...
- 20道Java面试必考题
系统整理了一下有关Java的面试题,包括基础篇,javaweb篇,框架篇,数据库篇,多线程篇,并发篇,算法篇等等,陆续更新中.其他方面如前端后端等等的面试题也在整理中,都会有的. 注:文末有福利!pd ...
- JAVA基础知识之多线程——线程的生命周期(状态)
线程有五个状态,分别是新建(New).就绪(Runnable).运行(Running).阻塞(Blocked)和死亡(Dead). 新建和就绪 程序使用new会新建一个线程,new出的对象跟普通对象一 ...
随机推荐
- 8.4 C++ 运算符重载
C/C++语言是一种通用的编程语言,具有高效.灵活和可移植等特点.C语言主要用于系统编程,如操作系统.编译器.数据库等:C语言是C语言的扩展,增加了面向对象编程的特性,适用于大型软件系统.图形用户界面 ...
- Docker从认识到实践再到底层原理(二-3)|LXC容器
前言 那么这里博主先安利一些干货满满的专栏了! 首先是博主的高质量博客的汇总,这个专栏里面的博客,都是博主最最用心写的一部分,干货满满,希望对大家有帮助. 高质量博客汇总 然后就是博主最近最花时间的一 ...
- CF1706E Qpwoeirut and Vertices 题解
题目链接:CF 或者 洛谷 官解看上去挺智慧的,来点朴素的解法.我们来当做纯 ds 题去做.首先明确一点,图中若干个点关于最早连通性的这个问题可以考虑 \(MST\),我们有一类东西叫 \(krusk ...
- Proxmox的local-lvm改文件存储,提升运行速度
介绍 Proxmox的缺省安装会创建 local 和 local-lvm 两个存储.其中local大约磁盘容量的10%,存储类别为目录. local-lvm的存储类别为 lvm-thin. 实际使用中 ...
- 17.2 内存映射文件的一致性--《Windows核心编程》
系统允许我们把同一个文件映射到多个视图中,只要映射的是同一个文件映射对象,系统会保证各视图中数据是一致的.例如一个程序修改了一个视图内的内容,那么系统会更新所有其他视图(对应同一文件映射对象)中的内容 ...
- macOS 上 常用的操作
首先 mac上 若使用的是windows的键盘,那么需要把ctrl 键,设置成 cmd键,因为mac上大多数操作都是 基于cmd键. 1.将ctrl键,修改为cmd键,这样后 复制.粘贴.剪切.全选等 ...
- ASP.NET Core分布式项目实战(业务介绍,架构设计,oAuth2,IdentityServer4)--学习笔记
任务4:第一章计划与目录 敏捷产品开发流程 原型预览与业务介绍 整体架构设计 API 接口设计 / swagger Identity Server 4 搭建登录 账号 API 实现 配置中心 任务5: ...
- 《ASP.NET Core 微服务实战》-- 读书笔记(第8章)
第 8 章 服务发现 面对大量服务,为了简化配置和管理工作,我们需要了解"服务发现"概念 回顾云原生特性 配置外置 将 URL 和登录凭证移到配置文件和 C# 代码之外,放到环境变 ...
- [JVM] CPU缓存一致性协议
CPU缓存一致性协议 CPU高速缓存 CPU缓存是位于cpu和内存之间的临时数据交换器,它的容量比内存小的夺但是交换速度要比内存快得多,主要是为了解决cpu运行时的处理速度与内存读写速度不匹配的问题. ...
- wmctf的题解&blindless&exit_hook
0x00 好久不见 2023.8.25 夜里 wmctf2023也是一个收获很大的比赛.只做了一个blindless,但是体会到了无泄露做出题来的奥妙.踩过的坑(学到的东西)包括但不限于 调试要用do ...