死磕 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,它的定位是为 ...
随机推荐
- this的绑定(四种绑定)+ 箭头函数 的this
一.this的默认绑定 当一个函数没有明确的调用对象的时候,也就是单纯作为独立函数调用的时候,将对函数的this使用默认绑定:绑定到全局的window对象 例子1: function foo(){ c ...
- 2016/09/29 SQL中的join
1.建表 首先建tb_a并插入数据. )); insertinto tb_a(symbol, sname) values ('A','B'); insertinto tb_a(symbol, snam ...
- Internet History,Technology,and Security - History: Commercialization and Growth(Week4)
Explosive Growth of the Internet and Web The Year of the Web 正如你所知道的,1994年是网络之年.1994年,NCSA的一位员工离职并建立 ...
- IDEA中使用Maven模板创建Maven WebApp项目并使用Tomact来运行项目
首先需要正确安装Maven和Tomact,Maven安装和Tomact安装步骤,参见别的文章. 一.创建Maven工作空间 点击Finish按钮后,耐心等待.直到出现BUILD SUCCESS为止. ...
- Nginx环境搭建与使用
一.背景 之前测试的项目前后端的"路由"(负责把前端发过来的请求转发到相应的后端服务上)要用Nignx来取代原来的tomcat的http server功能,做这个替换的原因是Nig ...
- 动态代理模式——JDK动态代理
今天,我就来讲一下动态代理的设计模式. 动态代理的意义在于生成一个代理对象,来代理真实对象,从而控制真实对象的访问.操作动态代理需要两个步骤:一.代理对象和真实对象建立代理关系.二.实现代理对象的代理 ...
- python数据挖掘第三篇-垃圾短信文本分类
数据挖掘第三篇-文本分类 文本分类总体上包括8个步骤.数据探索分析->数据抽取->文本预处理->分词->去除停用词->文本向量化表示->分类器->模型评估.重 ...
- 报错解决 unable to unroll loop, loop does not appear to terminate in a timely manner (994 iterations) or unrolled loop is too large, use the [unroll(n)] attribute to force an exact higher number
在 Unity 写 Shader 的时候,在一个循环里面使用了 tex2D 函数,类似与下面这样: fixed2 center = fixed2(0.5,0.5); fixed2 uv = i.uv ...
- Nginx+Tomcat8+Memcached实现负载均衡及session共享
1> 基础环境 简易拓扑图: 2> 部署Tomcat [root@node01 ~]# ll -h ~ |egrep 'jdk|tomcat'-rw-r--r-- 1 root root ...
- Python爬虫基础面试题,为2020年初就业做准备
前言本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理.作者:qiaoziheng 一.题目部分1.python中常用的数据结构有哪些 ...