死磕 java线程系列之线程的生命周期

(手机横屏看源码更方便)
注: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状态了,很神奇。

彩蛋
其实,本来这篇是准备写线程池的生命周期的,奈何线程的生命周期写了太多,等下一篇我们再来一起学习线程池的生命周期吧。
欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。

死磕 java线程系列之线程的生命周期的更多相关文章
- 死磕 java同步系列之AQS起篇
问题 (1)AQS是什么? (2)AQS的定位? (3)AQS的实现原理? (4)基于AQS实现自己的锁? 简介 AQS的全称是AbstractQueuedSynchronizer,它的定位是为Jav ...
- 死磕 java同步系列之volatile解析
问题 (1)volatile是如何保证可见性的? (2)volatile是如何禁止重排序的? (3)volatile的实现原理? (4)volatile的缺陷? 简介 volatile可以说是Java ...
- 死磕 java同步系列之自己动手写一个锁Lock
问题 (1)自己动手写一个锁需要哪些知识? (2)自己动手写一个锁到底有多简单? (3)自己能不能写出来一个完美的锁? 简介 本篇文章的目标一是自己动手写一个锁,这个锁的功能很简单,能进行正常的加锁. ...
- 死磕 java同步系列之CyclicBarrier源码解析——有图有真相
问题 (1)CyclicBarrier是什么? (2)CyclicBarrier具有什么特性? (3)CyclicBarrier与CountDownLatch的对比? 简介 CyclicBarrier ...
- 死磕 java同步系列之Phaser源码解析
问题 (1)Phaser是什么? (2)Phaser具有哪些特性? (3)Phaser相对于CyclicBarrier和CountDownLatch的优势? 简介 Phaser,翻译为阶段,它适用于这 ...
- 死磕 java同步系列之zookeeper分布式锁
问题 (1)zookeeper如何实现分布式锁? (2)zookeeper分布式锁有哪些优点? (3)zookeeper分布式锁有哪些缺点? 简介 zooKeeper是一个分布式的,开放源码的分布式应 ...
- 死磕 java同步系列之redis分布式锁进化史
问题 (1)redis如何实现分布式锁? (2)redis分布式锁有哪些优点? (3)redis分布式锁有哪些缺点? (4)redis实现分布式锁有没有现成的轮子可以使用? 简介 Redis(全称:R ...
- 死磕 java同步系列之终结篇
简介 同步系列到此就结束了,本篇文章对同步系列做一个总结. 脑图 下面是关于同步系列的一份脑图,列举了主要的知识点和问题点,看过本系列文章的同学可以根据脑图自行回顾所学的内容,也可以作为面试前的准备. ...
- 死磕 java同步系列之StampedLock源码解析
问题 (1)StampedLock是什么? (2)StampedLock具有什么特性? (3)StampedLock是否支持可重入? (4)StampedLock与ReentrantReadWrite ...
- 死磕 java同步系列之AQS终篇(面试)
问题 (1)AQS的定位? (2)AQS的重要组成部分? (3)AQS运用的设计模式? (4)AQS的总体流程? 简介 AQS的全称是AbstractQueuedSynchronizer,它的定位是为 ...
随机推荐
- NodeJS4-1静态资源服务器实战_实现访问获取里面的内容
.gitignore 匹配模式前 / 代表项目根目录 匹配模式最后加 / 代表是目录 匹配模式前加 ! 代表取反 * 代表任意一个字符 ? 匹配任意一个字符 ** 匹配多级目录 统一代码风格配置可以用 ...
- CCF-CSP题解 201703-3 Markdown
要求实现简易的Markdown到Html格式的转换. 主要就是字符串处理.以空行划分各个区块,区块内部字符的输出用一个\(print\_buf\)实现,至于强调和超链接的嵌套,则可以递归实现. 注意用 ...
- 自制导纳信号发生器 [原创cnblogs.com/helesheng]
最近正在研制一种通过测量人体导纳,估算体内血液变化率,进而评估心血管系统泵血功能的医疗仪器.为测量人体导纳,我们设计了一套巧妙的激励信号幅度反馈电路,该电路由于涉及商业机密就不在这里讨论了.这里主要分 ...
- Winform中实现批量文件复制(附代码下载)
场景 效果 将要批量复制的文件拖拽到窗体中,然后点击下边选择目标文件夹,然后点击复制按钮. 注: 博客主页: https://blog.csdn.net/badao_liumang_qizhi 关注公 ...
- 百度大脑UNIT3.0详解之数据生产工具DataKit
在智能对话项目搭建的过程中,高效筛选.处理对话日志并将其转化为新的训练数据,是对话系统效果持续提升的重要环节,也是当前开发者面临的难题之一.为此百度大脑UNIT推出学习反馈闭环机制,提供数据获取.辅助 ...
- ubuntu虚拟环境的操作
查看已经安装的虚拟环境:workon 退出虚拟环境,home目录下执行:deactivate 进入虚拟环境:workon 虚拟环境名
- 如何在 Chrome中导出、导入书签和密码
目录 书签 密码 书签 1.导出 点击浏览器右上角的三小点,选择"书签",再选择"书签管理器",进入如下页面 点击蓝色书签栏右上角的三小点,选择"导出 ...
- sudo go 找不到命令
我们使用 sudo 时,使用的配置文件是 /etc/sudoers. 解决配置 /etc/sudoers 文件中的 Defaults secure_path 这一项.将 $GOROOT/bin 目录加 ...
- 一分钟理解Java公平锁与非公平锁
本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...
- 在Vue中添加css扩展语言sass
npm install vue-loader --save-dev npm install node-sass --save-dev npm install sass-loader --save-de ...