JAVA进阶系列 - 并发编程 - 第3篇 线程的生命周期

目标

  1. 线程的生命周期

  2. 线程的状态定义

  3. 线程的状态转移

内容

1. 线程的生命周期说明

上一篇文章中,我们简单的描述了同步与异步的差异以及线程的基本使用。那么今天我们就来了解一下线程的生命周期。

在调用了 Thread 类对象的 start 方法来启动 Java 线程后,对应的底层操作系统线程不能马上得到 CPU 时间片来执行,需要等待操作系统的调度。所以,为了便于跟踪 Java 线程的执行情况,Thread 类定义了一系列的线程状态来表示当前线程的执行情况,同时整个线程的整个生命周期就是在这些状态直接转换。

2. 线程的状态定义

为了便于对 Java 的线程进行管理和对线程的运行情况进行跟踪,Java 定义了 6 个线程状态来表示线程的当前运行情况,这6个状态具体在 Thread 类的内部枚举类 State 中定义:

public enum State {
    // 新建状态
    NEW,
    // 可运行状态
    RUNNABLE,
    // 阻塞状态
    BLOCKED,
    // 等待状态
    WAITING,
    // 超时等待状态
    TIMED_WAITING,
    // 终止状态
    TERMINATED;
}

2.1. 新建状态 NEW

当我们用关键字 new 创建一个 Thread 对象时,因为没有调用 start 方法启动该线程,此时的 Thread 对象就是一个普通的 Java 对象,并不会执行任何操作。

@Slf4j
public class ThreadStateDemo {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("running...");
        }, "t1");

        log.debug("t1 state {}", t1.getState());    // t1 state NEW
    }
}


2.2. 可运行状态 RUNNABLE

线程通过调用 start 方法启动,使该线程对象进入 RUNNABLE 状态,此时才真正地在 JVM 进程中创建了一个线程。

但是线程启动之后可以立即得到执行吗?答案是否定的。线程的运行与否和进程一样都要听命于 CPU 的调度。如果操作系统的 CPU 此时是空闲的,则该线程对象对应的线程可以立即获取到 CPU 时间片并执行(此时的线程状态为运行中running)。反之,如果此时 CPU 繁忙,不能马上分配 CPU 时间片资源来执行该线程,则该线程需要等待操作系统之后的调度来获得 CPU 时间片并执行(此时的线程状态为就绪ready)。

@Slf4j
public class ThreadStateDemo {
    public static void main(String[] args) {
        Thread t2 = new Thread(() -> {
            while(true){
                // runnable
            }
        }, "t2");
        t2.start();

        log.debug("t2 state {}", t2.getState());    // t2 state RUNNABLE
    }
}


#### 2.3\. 阻塞状态 BLOCKED

当线程处于阻塞状态 BLOCKED 时,线程不能继续往下执行。这种情况主要出现在如下几种情况中:

  • 调用了 sleep 或者 wait 方法而加入了 waitSet 中;

  • 进行某个阻塞的 IO 操作;

  • 获取某个锁资源失败从而加入到该锁的阻塞队列中;

@Slf4j
public class ThreadStateDemo {
    public static void main(String[] args) {
        Thread t5 = new Thread(() -> {
            synchronized (ThreadStateDemo.class) {
                try {
                    // time_waiting
                    Thread.sleep(1000000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t5");
        t5.start();

        Thread t3 = new Thread(() -> {
            // t5 线程先占用锁,所以该线程会进入 blocked 状态
            synchronized (ThreadStateDemo.class) {
                try {
                    Thread.sleep(1000000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t3");
        t3.start();

        log.debug("t3 state {}", t3.getState());    // t3 state BLOCKED
    }
}


#### 2.4\. 等待状态 WAITING

线程处于等待状态 WAITING 主要出现在与其他线程进行协作的场景中,具体为当前线程等待其他线程执行某种操作来通知和唤醒当前线程。当前线程处于等待状态 WAITING 时,除了不能继续往下执行外,还有个特性是如果当前线程持有锁,如在 synchronized 方法内或者 synchronized 同步代码块内部执行时,线程进入了等待状态,则会自动释放锁,这样其他线程可以竞争获取该锁。

在 Thread 类的方法设计中,主要是基于 Object 类的 wait、notify 和 notifyAll 方法实现的,其中线程调用 wait 方法时,进入等待状态。另外当线程调用 Thread 类的 join 方法或者调用 LockSupport 的静态 park 方法时,线程也会进入等待状态 WAITING。

@Slf4j
public class ThreadStateDemo {
    public static void main(String[] args) {
        Thread t2 = new Thread(() -> {
            while(true){
                // runnable
            }
        }, "t2");
        t2.start();

        Thread t4 = new Thread(() -> {
            try {
                // waiting
                t2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t4");
        t4.start();

        log.debug("t4 state {}", t4.getState());    // t4 state WAITING
    }
}


#### 2.5\. 超时等待状态 TIMED_WAITING

超时等待状态 TIMED_WAITING 与等待状态 WAITING 功能类似,不同之处在于 WAITING 状态的等待不支持指定等待时间,没有超时机制,一旦条件不满足就会无限等待下去。而处于超时等待状态 TIMED_WAITING 的线程,指定了最长等待时间,如果超过这个时间条件还未满足,则当前线程会自动唤醒并继续往下执行。

@Slf4j
public class ThreadStateDemo {
    public static void main(String[] args) {
        Thread t5 = new Thread(() -> {
            synchronized (ThreadStateDemo.class) {
                try {
                    // time_waiting
                    Thread.sleep(1000000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t5");
        t5.start();

        log.debug("t5 state {}", t5.getState());    // t5 state TIMED_WAITING
    }
}


#### 2.6\. 终止状态 TERMINATED

TERMINATED 是一个线程的最终状态。当 Thread 线程对象对应的现场执行完毕时,线程进入终止状态 TERMINATED,在该状态中线程将不会切换到其他任何状态。这意味着该线程整个生命周期都结束了,对应的 Thread 类对象也会被回收销毁。下列这些情况都将会使线程进入 TERMINATED 状态:

  • 线程正常运行结束;

  • 线程意外出错结束;

  • JVM Crash,导致所有线程结束;

@Slf4j
public class ThreadStateDemo {
    public static void main(String[] args) {
        Thread t6 = new Thread(() -> {
            log.debug("running...");
        }, "t6");
        t6.start();

        try {
            // 这里主线程休眠一下,让
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        log.debug("t6 state {}", t6.getState());    // t6 state TERMINATED
    }
}


### 3\. 线程的状态转换

JAVA进阶系列 - 并发编程 - 第3篇 线程的生命周期

图片来源:B站 - 黑马程序员

上图中箭头表示可由该状态向对应状态转换。其中,RUNNABLE 状态涵盖了操作系统层面的 【可运行状态】【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在Java里面无法区分,仍然认为是可运行)

总结

本篇我们学习了线程的生命周期,在使用多线程的过程中,线程的生命周期将会贯穿始终,只有清晰地掌握生命周期各个阶段的切换,才能更好地理解线程的阻塞以及唤醒机制,同时也为掌握同步锁等概念打下一个良好的基础。

今天的文章到这里就结束了,小伙伴们有什么建议或者意见请联系我改进哦,微信公众号:【该昵称无法识别】,你们的支持是我最大的动力!!!

JAVA进阶系列 - 并发编程 - 第3篇 线程的生命周期

上一篇:Java中多线程的六种状态详解


下一篇:java线程生命周期