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

     2022-04-01     132

关键词:

技术图片

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


注: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)线程有哪些类型?(2)线程模型有哪些?(3)各语言是怎么实现自己的线程模型的?简介在Java中,我们平时所说的并发编程、多线程、共享资源等概念都是与线程相关的,这里所说的线程实际上应该叫作“用户线程... 查看详情

死磕java线程系列之创建线程的8种方式

问题(1)创建线程有哪几种方式?(2)它们分别有什么运用场景?简介创建线程,是多线程编程中最基本的操作,彤哥总结了一下,大概有8种创建线程的方式,你知道吗?继承Thread类并重写run()方法publicclassCreatingThread01extendsThr... 查看详情

死磕java线程系列之线程池深入解析——未来任务执行流程

...怎么实现的呢?建议学习本章前先去看看彤哥之前写的《死磕java线程系列之自己动手写一个线程池 查看详情

死磕java线程系列之线程池深入解析——体系结构

(手机横屏看源码更方便)注:java源码分析部分如无特殊说明均基于java8版本。简介Java的线程池是块硬骨头,对线程池的源码做深入研究不仅能提高对Java整个并发编程的理解,也能提高自己在面试中的表现,增加被录取的可能... 查看详情

死磕java线程系列之线程池深入解析——构造方法

(手机横屏看源码更方便)注:java源码分析部分如无特殊说明均基于java8版本。简介ThreadPoolExecutor的构造方法是创建线程池的入口,虽然比较简单,但是信息量很大,由此也能引发一系列的问题,同样地,这也是面试中经常被问... 查看详情

死磕java线程系列之自己动手写一个线程池(代码片段)

欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章,与彤哥一起畅游源码的海洋。(手机横屏看源码更方便)问题(1)自己动手写一个线程池需要考虑哪些因素?(2)自己动手写的线程池如何测试?简介线程池是Java... 查看详情

死磕java线程系列之自己动手写一个线程池(代码片段)

(手机横屏看源码更方便)问题(1)自己动手写一个线程池需要考虑哪些因素?(2)自己动手写的线程池如何测试?简介线程池是Java并发编程中经常使用到的技术,那么自己如何动手写一个线程池呢?本文彤哥将手把手带你写... 查看详情

死磕java线程系列之线程池深入解析——定时任务执行流程

(手机横屏看源码更方便)注:java源码分析部分如无特殊说明均基于java8版本。注:本文基于ScheduledThreadPoolExecutor定时线程池类。简介前面我们一起学习了普通任务、未来任务的执行流程,今天我们再来学习一种新的任务——定... 查看详情

死磕java线程系列之自己动手写一个线程池(续)(代码片段)

(手机横屏看源码更方便)问题(1)自己动手写的线程池如何支持带返回值的任务呢?(2)如果任务执行的过程中抛出异常了该怎么处理呢?简介上一章我们自己动手写了一个线程池,但是它是不支持带返回值的任务的,那么... 查看详情

死磕java线程系列之自己动手写一个线程池(续)(代码片段)

(手机横屏看源码更方便)问题(1)自己动手写的线程池如何支持带返回值的任务呢?(2)如果任务执行的过程中抛出异常了该怎么处理呢?简介上一章我们自己动手写了一个线程池,但是它是不支持带返回值的任务的,那么... 查看详情

java多线程之线程生命周期

一、Java线程的调度方法:1、同优先级线程组成先进先出队列(先到先服务),使用时间片策略。2、对高优先级,使用优先调度的抢占式策略。二、Java线程的优先级:1、线程的优先级等级MAX_PRIORITY:10、MIN_PRIORITY:1、NORMPRIORITY:52、涉... 查看详情

java并发系列-----多线程简介创建以及生命周期(代码片段)

进程、线程与任务进程:程序的运行实例。打开电脑的任务管理器,如下:正在运行的360浏览器就是一个进程。运行一个java程序的实质是启动一个java虚拟机进程,也就是说一个运行的java程序就是一个java虚拟机进程。进程是程... 查看详情

java多线程基础--线程生命周期与线程协作详解(代码片段)

前言各位亲爱的读者朋友,我正在创作Java多线程系列文章,本篇我们将梳理基础内容:线程生命周期与线程协作这是非常基础的内容,本篇仅从知识完整性角度出发,做一次梳理。作者按:本篇按照自己... 查看详情

java多线程基础--线程生命周期与线程协作详解(代码片段)

前言各位亲爱的读者朋友,我正在创作Java多线程系列文章,本篇我们将梳理基础内容:线程生命周期与线程协作这是非常基础的内容,本篇仅从知识完整性角度出发,做一次梳理。作者按:本篇按照自己... 查看详情

java并发编程系列之二线程基础

...文章对并发的理论基础进行了回顾,主要是为什么使用多线程、多线程会引发什么问题及引发的原因,和怎么使用Java中的多线程去解决这些问题。正所谓,知其然知其所以然,这是学习一个知识遵循的原则。推荐读者先行查看... 查看详情

java多线程系列--“juc线程池”04之线程池原理

转自:http://www.cnblogs.com/skywang12345/p/3509960.html本章介绍线程池的生命周期。在"Java多线程系列--“基础篇”01之基本概念"中,我们介绍过,线程有5种状态:新建状态,就绪状态,运行状态,阻塞状态,死亡状态。线程池也有5种状... 查看详情

java多线程系列--“juc线程池”04之线程池原理

本章介绍线程池的生命周期。线程有5种状态:新建状态,就绪状态,运行状态,阻塞状态,死亡状态。线程池也有5种状态;然而,线程池不同于线程,线程池的5种状态是:Running, SHUTDOWN, STOP, TIDYING, TERMINATED。线... 查看详情

死磕java同步系列之volatile解析(代码片段)

问题(1)volatile是如何保证可见性的?(2)volatile是如何禁止重排序的?(3)volatile的实现原理?(4)volatile的缺陷?简介volatile可以说是Java虚拟机提供的最轻量级的同步机制了,但是它并不容易被正确地理解,以至于很多人不... 查看详情