(手机横屏看源码更方便)


注: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线程系列之线程的生命周期的更多相关文章

  1. 死磕 java同步系列之AQS起篇

    问题 (1)AQS是什么? (2)AQS的定位? (3)AQS的实现原理? (4)基于AQS实现自己的锁? 简介 AQS的全称是AbstractQueuedSynchronizer,它的定位是为Jav ...

  2. 死磕 java同步系列之volatile解析

    问题 (1)volatile是如何保证可见性的? (2)volatile是如何禁止重排序的? (3)volatile的实现原理? (4)volatile的缺陷? 简介 volatile可以说是Java ...

  3. 死磕 java同步系列之自己动手写一个锁Lock

    问题 (1)自己动手写一个锁需要哪些知识? (2)自己动手写一个锁到底有多简单? (3)自己能不能写出来一个完美的锁? 简介 本篇文章的目标一是自己动手写一个锁,这个锁的功能很简单,能进行正常的加锁. ...

  4. 死磕 java同步系列之CyclicBarrier源码解析——有图有真相

    问题 (1)CyclicBarrier是什么? (2)CyclicBarrier具有什么特性? (3)CyclicBarrier与CountDownLatch的对比? 简介 CyclicBarrier ...

  5. 死磕 java同步系列之Phaser源码解析

    问题 (1)Phaser是什么? (2)Phaser具有哪些特性? (3)Phaser相对于CyclicBarrier和CountDownLatch的优势? 简介 Phaser,翻译为阶段,它适用于这 ...

  6. 死磕 java同步系列之zookeeper分布式锁

    问题 (1)zookeeper如何实现分布式锁? (2)zookeeper分布式锁有哪些优点? (3)zookeeper分布式锁有哪些缺点? 简介 zooKeeper是一个分布式的,开放源码的分布式应 ...

  7. 死磕 java同步系列之redis分布式锁进化史

    问题 (1)redis如何实现分布式锁? (2)redis分布式锁有哪些优点? (3)redis分布式锁有哪些缺点? (4)redis实现分布式锁有没有现成的轮子可以使用? 简介 Redis(全称:R ...

  8. 死磕 java同步系列之终结篇

    简介 同步系列到此就结束了,本篇文章对同步系列做一个总结. 脑图 下面是关于同步系列的一份脑图,列举了主要的知识点和问题点,看过本系列文章的同学可以根据脑图自行回顾所学的内容,也可以作为面试前的准备. ...

  9. 死磕 java同步系列之StampedLock源码解析

    问题 (1)StampedLock是什么? (2)StampedLock具有什么特性? (3)StampedLock是否支持可重入? (4)StampedLock与ReentrantReadWrite ...

  10. 死磕 java同步系列之AQS终篇(面试)

    问题 (1)AQS的定位? (2)AQS的重要组成部分? (3)AQS运用的设计模式? (4)AQS的总体流程? 简介 AQS的全称是AbstractQueuedSynchronizer,它的定位是为 ...

随机推荐

  1. 更改CSDN博客皮肤的一种简易方法

    CSDN改版后,皮肤设置变得不能够更改了,不过下面这种方法依然可以做到: 首先来到博客设置的主页面:. 接下来按ctrl + shift + i进入 如下页面,然后点击图中红色标记圈起来的选择元素按钮 ...

  2. 2019最新EI源刊目录

    2D Materials Journal3D Printing and Additive Manufacturing Journal3D Research Journal3DTV-Conference ...

  3. Axure5.1.0.1699 RP汉化版

    Axure是一款产品原型(Prototype)设计软件,可通过这个软件编辑一些常见的事件和在特点条件下才会触发的情况,可以快速的生成HTML Demo页面. 使用Axure的用户很多,据淘宝UED消息 ...

  4. spark-3.0 application 调度算法解析

    spark 各个版本的application 调度算法还是有这明显的不同之处的.从spark1.3.0 到 spark 1.6.1.spark2.0 到 现在最新的spark 3.0 ,调度算法有了一 ...

  5. RN配置 java和python环境

    安装java JDK 不推荐更改安装路径. 安装时 不要有中文 会安装jdK和jre 1==>配置新建 JAVA_HOME 具体是[编辑]==>[新建] 然后添加下两句 JAVA_HOME ...

  6. Angular 彻底解决 Dropdown 在 Safari 上无法自动关闭的问题

    之前在 Safari 上用 focus 事件来实现 Dropdown 下拉菜单,结果在 iOS 上不兼容. 尝试了 MDN 和 stack over flow 上各种奇技淫巧,然而在 iOS 上全都败 ...

  7. Java连载60-类之间的六种关系

    一.类之间的关系 1.泛化关系(UML图:实线空心三角形箭头,箭头指向被继承方) 类和类.接口与接口之间的继承称为泛化关系 public class A {} class B extends A{} ...

  8. P1983 车站分级 思维+拓扑排序

    很久以前的一道暑假集训的题,忘了补. 感觉就是思维建图,加拓扑排序. 未停靠的火车站,必然比停靠的火车站等级低,就可以以此来建边,此处注意用vis来维护一下,一个起点和终点只建立一条边,因为不这样的话 ...

  9. 移动端H5页面开发,碰到一个字体变大的BUG

    移动端H5页面开发,碰到一个字体变大的BUG webkit内核下,对不定高宽的元素可能会放大其字体.那么,就可以设置一个max-width:或者使用-webkit-text-size-adjust: ...

  10. 一起学SpringMVC之Request方式

    本文主要以一些简单的小例子,简述在SpringMVC开发过程中,经常用到的Request方面的内容,仅供学习分享使用,如有不足之处,还请指正. 概述 在客户机和服务器之间进行请求-响应时,两种最常被用 ...