秒懂 Java Thread 生命周期

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

本文中我想详细的讨论下 Java 中的核心概念 - 线程的生命周期。我会使用一张我自制的图片加上实用的代码片段一步一步的详细剖析线程的各个状态和各个状态之间如何转换。

Java 中的多线程

Java 语言中 多线程是由 Thread 的核心概念驱动的。因为多线程中的每一个线程都相互独立有着自己的生命周期和状态转换。

Java 线程中的生命周期

Java 中每一个线程都是 java.lang.Thread 类的实例。而且Java 个线程生命周期中的各个状态都定义在 Thread 类的一个静态的 State 枚举中。

State 枚举定义了线程的所有潜在状态。总共有 6 个分别对应者上图中的 6 个绿色背景的矩形和椭圆型。

  • NEW : 新创建的且未调用 start() 方法开始执行的线程。
  • RUNNABLE : 已经在运行中的线程或正在等待资源分配的准备运行的线程。
  • BLOCKED : 等待获取进入或重新进入同步块或方法的监视器锁的线程。
  • WAITING : 等待其他一些线程执行特定操作没有任何时间限制。
  • TIMED_WAITING: 等待某个其他线程在指定时间段内执行特定操作
  • TERMINATED : 线程完成了它的任务。

需要注意的是: 在任何给定的时间点线程只能处于这些状态之一

  • NEW 状态应该很好理解比如车厂家生产出来只要还没被卖出过那么它就是新的 ( NEW )
  • RUNNABLE 只要线程不出于其它状态它就是 RUNNABLE 状态。怎么理解呢? 车买来了只要它没坏没出什么毛病没借给别人那么它就出于可开状态不管是呆在家里吃灰还是已经在上路运行。
  • WAITING : 无时间显示的等待其它线程完成任务时就处于这个状态怎么理解呢?比如长假告诉公路大堵车要等待别人前进了几个蜗牛步我们才能往前几个蜗牛步有时候一等就是昏天暗地可能长达几天也可能一辈子吧。
  • TIMED_WAITING : 一直处于 WAITING 总不是办法所以可能会设置一个超时时间如果过了时间就不等待了。同样的如果可以后退那么我们在堵车的时候可能会等待那么十几分钟发现确实走不了就等了呗。
  • TERMINATED : 当一个线程结束了它的任务(可能完成了也可能没完成就会处于这个状态。如果拿车做比喻那么当车彻底报废已经再也不能上路了就处于这个状态。

其实拿车作比喻感觉有点怪我觉得拿追女朋友来做比喻比较恰当些。

NEW 状态

NEW状态的线程(或已经创建的新线程是已创建但尚未启动的线程。线程会一直保持这个 NEW 状态直到在该线程上调用了 start() 方法启动它。

下面的代码我们创建了一个 NEW 状态的线程

Runnable runnable = new NewState();
Thread t = new Thread(runnable);
Log.info(t.getState());

由于我们没有启动线程因此 t.getState() 方法将打印输出

NEW

RUNNABLE 状态

当在一个 NEW 状态的线程上调用 start() 方法时该线程的状态会从 NEW 转换为 RUNNABLE。处于该状态的线程要么是已经在运行中那么是在处于正在等待系统的资源分配(准备运行。

在多线程环境中线程调度器 ( Thread-Scheduler它是 JVM 的一部分会为每个线程分配固定的时间。线程并不是一直都在执行的调度器会把暂时空闲的线程的 CPU ( 还是在 RUNNABLE 状态 让出来让其它需要的线程去运行。因此它会运行一段特定的时间然后将控制权放弃给其他 RUNNABLE 线程。

注意: 这里的等待资源不是等待其它线程而是等待 CPU 排队。打个比方新车上路。要等待的是有没有路如果没有路就开不了这是本质的问题。

例如让我们将 t.start() 方法添加到我们之前的代码中并尝试访问其当前状态

Runnable runnable = new NewState();
Thread t = new Thread(runnable);
t.start();
Log.info(t.getState());

此代码最有可能返回输出

RUNNABLE

为什么说是最有可能呢?如果是一个空转线程除了 CPU 不需要其它资源那么很大概率就是 RUNNABLE 但如果需要其它资源可能会因为竞争资源而处于其它状态。还有一种情况可能还没运行到 t.getState() 线程任务就执行完毕了那么也不会是 RUNNABLE 状态。

BLOCKED 状态

当一个线程当前没有资格运行时它处于 BLOCKED 状态。如果线程在尝试访问由某个其他线程锁定的代码段时那它会因为需要等待获取监视器锁进入此状态。

我们使用一小段代码来重现下这个状态

public class BlockedState {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new DemoThreadB());
        Thread t2 = new Thread(new DemoThreadB());

        t1.start();
        t2.start();

        Thread.sleep(1000);

        Log.info(t2.getState());
        System.exit(0);
    }
}

class DemoThreadB implements Runnable {
    @Override
    public void run() {
        commonResource();
    }

    public static synchronized void commonResource() {
        while(true) {
            // Infinite loop to mimic heavy processing
            // 't1' won't leave this method
            // when 't2' try to enters this
        }
    }
}

在上面这段代码中

  • 我们创建了两个不同的线程-- t1 和 t2 。
  • t1 启动后就进入了同步的 commonResource()方法同步方法意味着一次只能有一个线程可以访问它。尝试访问此方法的所有其他后续线程将被阻止进一步执行直到当前线程完成处理。
  • 当 t1 进入这个方法时它保持了无限循环这只是为了模仿繁重的处理以便所有其他线程都无法进入此方法。
  • 接着我们开启 t2 它尝试输入已经被 t1 访问的 commonResource() 方法这时因为 commonResource() 被 t1 锁定所以 t2 将保持在 BLOCKED 状态

在这个状态上当我们使用 t.getState() 时将输出

BLOCKED

WAITTING 状态

线程在等待某个其他线程执行特定操作时处于 WAITING 状态。根据 Oracle 官方文档任何线程都可以通过调用以下三种方法中的任何一种来进入此状态:

1、 object.wait();
2、 thread.join();
3、 LockSupport.park();

请注意我们没有为 wait() 和 join() 定义任何超时时间因为下一节将介绍该方案。

我们以后会写一个单独的教程详细讨论了 wait()、notify() 和 notifyAll() 的使用。

下面我们写一段代码尝试重现这种状态

public class WaitingState implements Runnable {
    public static Thread t1;

    public static void main(String[] args) {
        t1 = new Thread(new WaitingState());
        t1.start();
    }

    public void run() {
        Thread t2 = new Thread(new DemoThreadWS());
        t2.start();

        try {
            t2.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            Log.error("Thread interrupted", e);
        }
    }
}

class DemoThreadWS implements Runnable {
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            Log.error("Thread interrupted", e);
        }

        Log.info(WaitingState.t1.getState());
    }
}

我们来讨论一下上面的代码做的事情

1、 首先我们创建并启动了t1;
2、 其次t1创建了t2并启动它;
3、 当t2的处理继续时我们调用t2.join()这使t1处于WAITING状态直到t2完成执行;
4、 由于t1正在等待t2完成我们从t2调用t1.getState();

输出结果一般为

WAITING

请留意在哪里调用 t1.getState() 。

所以WAITING 和 BLOCKED 两个状态的区别是什么?

  • BLOCKED 是因为线程竞争不到资源而处于 BLOCKED 状态。这个是被动的。因为别无选择。
  • WAITING 是因为线程主动等待别人完成而处于 WAITING 状态。这个是主动的。因为它可以不调用那三个方法不用等待其它人完成。它可以选择挥一挥衣袖不不带走一片云彩

TIMED_WAITING 状态

线程在等待另一个线程在规定的时间内执行特定操作时处于 TIMED_WAITING 状态。根据 Java Docs 文档有五种方法可以将线程置于TIMED_WAITING 状态:

1、 thread.sleep(longmillis);
2、 wait(inttimeout)orwait(inttimeout,intnanos);
3、 thread.join(longmillis);
4、 LockSupport.parkNanos;
5、 LockSupport.parkUntil;

下面我们写一段代码尝试重现这种状态

public class TimedWaitingState {
    public static void main(String[] args) throws InterruptedException {
        DemoThread obj1 = new DemoThread();
        Thread t1 = new Thread(obj1);
        t1.start();

        // The following sleep will give enough time for ThreadScheduler
        // to start processing of thread t1
        Thread.sleep(1000);
        Log.info(t1.getState());
    }
}

class DemoThread implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            Log.error("Thread interrupted", e);
        }
    }
}

整体代码和 WAITING 状态的差不多我们创建并启动了一个线程 t1并它进入睡眠状态超时时间为 5 秒。

输出结果为

TIMED_WAITING

TERMINATED 状态

这是一个 「 已死 」 线程的状态。当一个线程已经完成执行或异常终止时它处于 TERMINATED 状态。我们在 怎么关闭一个 Java 线程 ( Thread ) ? 一文中讨论过杀死线程的不同方法。

这个状态没什么好讨论的我们写一段代码尝试重现这种状态

public class TerminatedState implements Runnable {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new TerminatedState());
        t1.start();
        // The following sleep method will give enough time for 
        // thread t1 to complete
        Thread.sleep(1000);
        Log.info(t1.getState());
    }

    @Override
    public void run() {
        // No processing in this block
    }
}

上面这段代码中我们启动线程 t1 时下一个语句 Thread.sleep(1000) 为 t1 提供了足够的时间来完成。

因此上面这个示例输出结果为

TERMINATED

后记

在本教程中我们了解了 Java 中线程的生命周期。我们详细介绍了 Thread.State 枚举定义的所有七个状态并使用一些示例来演示他们。

虽然代码片段几乎可以在每台机器上提供相同的输出但在某些特殊情况下我们可能会得到一些不同的输出因为线程调度程序的确切行为无法确定。

所以有任何问题欢迎回帖咨询

 

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Java