侧边栏壁纸
博主头像
翻斗

开始一件事最好是昨天,其次是现在

  • 累计撰写 44 篇文章
  • 累计创建 42 个标签
  • 累计收到 3 条评论

Java线程的生命周期

翻斗
2020-03-15 / 0 评论 / 0 点赞 / 422 阅读 / 4,822 字

Baeldung 翻译系列之Java并发基础:

Java中的concurrent包
Java中的Synchronized关键字
Future介绍
ThreadLocal介绍
Java线程的生命周期
如何杀掉一个Java线程
Java中的线程池介绍
实现Runnable接口还是继承Thread类
Java中的wait和notify方法
Runnable vs Callable
wait和sleep的区别
Thread.join方法介绍
Java中使用锁对象
ThreadPoolTaskExecutor中的corePoolSize和maxPoolSize
Java中的异步编程

1. 介绍

在这篇文章中,我们将详细讨论Java中的一个核心概念 - 线程的生命周期。

我们将使用一个快速的图解和实用的代码片段来更好地理解线程执行过程中的这些状态。

要开始理解Java中的线程,这篇关于创建线程的文章是一个很好的起点。

2. Java中的多线程

在Java语言中,多线程是由核心概念“线程”驱动的。在它们的生命周期中,线程会经历各种状态:

3. Java中线程的生命周期

java.lang.Thread 类包含一个静态的 State 枚举 - 定义了线程可能的状态。在任何给定的时间点,线程只能处于以下这些状态中的一个:

  • NEW - 新创建的线程,尚未开始执行
  • RUNNABLE - 正在运行或准备执行,但正在等待资源分配
  • BLOCKED - 等待获取监视器锁以进入或重新进入一个同步块/方法
  • WAITING - 等待其他线程执行特定操作,没有时间限制
  • TIMED_WAITING - 等待其他线程在指定的时间内执行特定操作
  • TERMINATED - 已完成执行
    所有这些状态都在上面的图表中有所涵盖;现在让我们详细讨论每一个状态。

3.1. New

一个NEW(新的)线程(或者说是刚生成的线程)是一个已经创建但尚未启动的线程。它会保持在这个状态,直到我们使用 start() 方法启动它。

以下代码片段展示了一个处于NEW状态的新创建的线程:

Runnable runnable = new NewState();
Thread t = new Thread(runnable);
System.out.println(t.getState());

由于我们没有启动上述线程,所以方法 t.getState() 将打印出:

NEW

3.2. Runnable

当我们创建了一个新线程并在其上调用 start() 方法后,它就从 NEW 状态转变为 RUNNABLE 状态。处于此状态的线程要么正在运行,要么准备运行,但它们正在等待系统的资源分配

在多线程环境中,线程调度器(它是JVM的一部分)为每个线程分配固定的时间。因此,它运行一段特定的时间,然后将控制权交给其他 RUNNABLE 状态的线程。

例如,让我们在前面的代码中添加 t.start() 方法,然后尝试访问它的当前状态:

Runnable runnable = new NewState();
Thread t = new Thread(runnable);
t.start();
System.out.println(t.getState());

这段代码最有可能的输出是:

RUNNABLE

请注意,在这个例子中,我们不能保证当我们的控制权到达 t.getState() 时,它仍然处于 RUNNABLE 状态。

可能的情况是,它被线程调度器立即调度并可能完成执行。在这种情况下,我们可能会得到不同的输出。

3.3. Blocked

当线程当前无法运行时,它就处于 BLOCKED 状态。当它等待监视器锁并试图访问由其他线程锁定的代码段时,它会进入这个状态。

让我们尝试复现这个状态:

public class BlockedState {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new DemoBlockedRunnable());
        Thread t2 = new Thread(new DemoBlockedRunnable());
        
        t1.start();
        t2.start();
        
        Thread.sleep(1000);
        
        System.out.println(t2.getState());
        System.exit(0);
    }
}

class DemoBlockedRunnable 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 enter this
        }
    }
}

在这段代码中:

我们创建了两个不同的线程 - t1t2
t1 开始并进入同步的 commonResource() 方法;这意味着只有一个线程可以访问它;所有其他尝试访问此方法的后续线程都将被阻止进一步执行,直到当前线程完成处理
t1 进入这个方法时,它被保持在一个无限的 while 循环中;这只是为了模拟重型处理,以便所有其他线程不能进入这个方法
现在当我们启动 t2 时,它试图进入 commonResource() 方法,这个方法已经被 t1 访问,因此,t2 将被保持在 BLOCKED 状态
在这个状态下,我们调用 t2.getState() 并得到输出为:

BLOCKED

3.4. Waiting

当线程等待其他线程执行特定操作时,它就处于 WAITING 状态。根据 JavaDocs,任何线程可以通过调用以下三种方法中的任何一种进入此状态:

  • object.wait()
  • thread.join()
  • 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 DemoWaitingStateRunnable());
        t2.start();

        try {
            t2.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
    }
}

class DemoWaitingStateRunnable implements Runnable {
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
        
        System.out.println(WaitingState.t1.getState());
    }
}

让我们讨论一下我们在这里做了什么:

  • 我们创建并启动了 t1
  • t1 创建了一个 t2 并启动它
  • t2 的处理继续时,我们调用 t2.join(),这会使 t1 进入 WAITING 状态,直到 t2 完成执行
  • 由于 t1 正在等待 t2 完成,所以我们从 t2 调用 t1.getState()
    这里的输出正如你所期望的:
WAITING

3.5. Timed Waiting

当线程在规定的时间内等待其他线程执行特定操作时,它就处于 TIMED_WAITING 状态。

根据 JavaDocs,有五种方法可以将线程置于 TIMED_WAITING 状态:

  • thread.sleep(long millis)
  • wait(int timeout) 或 wait(int timeout, int nanos)
  • thread.join(long millis)
  • LockSupport.parkNanos
  • LockSupport.parkUntil
    要了解更多关于 Java 中 wait()sleep() 的区别,请查看这篇专门的文章。

现在,让我们尝试快速复现这个状态:

public class TimedWaitingState {
    public static void main(String[] args) throws InterruptedException {
        DemoTimeWaitingRunnable runnable= new DemoTimeWaitingRunnable();
        Thread t1 = new Thread(runnable);
        t1.start();
        
        // The following sleep will give enough time for ThreadScheduler
        // to start processing of thread t1
        Thread.sleep(1000);
        System.out.println(t1.getState());
    }
}

class DemoTimeWaitingRunnable implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
    }
}

在这里,我们创建并启动了一个线程 t1,它进入了休眠状态,超时期为5秒;输出将是:

TIMED_WAITING

3.6. Terminated

这是一个死亡线程的状态。当它已经完成执行或者被非正常终止时,它就处于 TERMINATED 状态。

让我们尝试在以下示例中实现这个状态:

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);
        System.out.println(t1.getState());
    }
    
    @Override
    public void run() {
        // No processing in this block
    }
}

在这里,虽然我们已经启动了线程 t1,但是紧接着的语句 Thread.sleep(1000) 给了 t1 足够的时间来完成,所以这个程序给我们的输出是:

TERMINATED

除了线程状态,我们还可以检查 isAlive() 方法来确定线程是否存活。例如,如果我们在这个线程上调用 isAlive() 方法:

Assert.assertFalse(t1.isAlive());

它会返回 false。简单来说,只有当线程已经启动并且还没有死亡时,线程才是活动的。

4. 总结

在这个教程中,我们学习了Java中线程的生命周期。我们查看了由Thread.State枚举定义的所有六种状态,并通过快速示例复现了它们。

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

和往常一样,这里使用的代码片段可以在GitHub 或者 Gitee上找到。

0

评论区