MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java 线程池状态切换机制

2024-10-257.0k 阅读

Java 线程池状态概述

在 Java 中,线程池是一种管理和复用线程的机制,能有效提高应用程序的性能和资源利用率。线程池的状态是其运行过程中的重要属性,不同状态反映了线程池当前的工作情况以及可以执行的操作。线程池主要有以下几种状态:

  1. RUNNING:这是线程池的初始状态,线程池在创建后就处于此状态。在 RUNNING 状态下,线程池可以接受新任务,并处理阻塞队列中的任务。
  2. SHUTDOWN:调用线程池的 shutdown() 方法后,线程池进入此状态。此时,线程池不再接受新任务,但会继续处理阻塞队列中已有的任务。
  3. STOP:调用线程池的 shutdownNow() 方法后,线程池进入 STOP 状态。在这种状态下,线程池不仅不再接受新任务,还会中断正在执行的任务,并清空阻塞队列。
  4. TIDYING:当所有任务都已终止(包括正在执行的任务被中断和阻塞队列中的任务被处理完毕),并且工作线程数为 0 时,线程池会进入 TIDYING 状态。
  5. TERMINATED:在线程池进入 TIDYING 状态后,会执行一个钩子方法 terminated(),当这个方法执行完毕,线程池就进入 TERMINATED 状态,这表示线程池彻底终止。

线程池状态的存储与表示

Java 线程池的状态信息被编码存储在一个原子变量 ctl 中。ctl 是一个 AtomicInteger 类型的变量,它使用了 32 位中的高三位来表示线程池状态,低 29 位表示工作线程的数量。这种设计方式使得对线程池状态和工作线程数量的操作可以在一个原子操作中完成,提高了并发操作的效率和安全性。

下面是 ctl 相关的一些定义:

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// 线程池状态的掩码,高三位
private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;

// Packing and unpacking ctl
private static int ctlOf(int rs, int wc) { return rs | wc; }
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }

在上述代码中,ctlOf 方法用于将线程池状态和工作线程数合并成 ctl 的值,runStateOf 方法用于从 ctl 中提取线程池状态,workerCountOf 方法用于从 ctl 中提取工作线程数。

RUNNING 状态

线程池在创建时,默认处于 RUNNING 状态。例如,使用 ThreadPoolExecutor 的构造函数创建线程池时:

ThreadPoolExecutor executor = new ThreadPoolExecutor(
        2, // corePoolSize
        4, // maximumPoolSize
        10, // keepAliveTime
        TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(10));

在 RUNNING 状态下,线程池可以接受新任务并处理阻塞队列中的任务。当调用 execute(Runnable task) 方法提交任务时,如果当前工作线程数小于 corePoolSize,线程池会创建新的工作线程来执行任务;如果当前工作线程数大于等于 corePoolSize 且阻塞队列未满,任务会被放入阻塞队列;如果阻塞队列已满且当前工作线程数小于 maximumPoolSize,则会创建新的工作线程来执行任务;如果阻塞队列已满且当前工作线程数大于等于 maximumPoolSize,则会根据饱和策略来处理任务。

SHUTDOWN 状态

当调用线程池的 shutdown() 方法时,线程池会从 RUNNING 状态转变为 SHUTDOWN 状态。在 SHUTDOWN 状态下,线程池不再接受新任务,但会继续处理阻塞队列中已有的任务。以下是一个示例:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ShutdownExample {
    public static void main(String[] args) {
        BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(10);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2, // corePoolSize
                4, // maximumPoolSize
                10, // keepAliveTime
                TimeUnit.SECONDS,
                queue);

        // 提交一些任务
        for (int i = 0; i < 15; i++) {
            int taskNumber = i;
            executor.execute(() -> {
                System.out.println("Task " + taskNumber + " is running on " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }

        // 调用 shutdown 方法
        executor.shutdown();

        // 等待所有任务执行完毕
        try {
            if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                executor.shutdownNow();
                if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                    System.err.println("Pool did not terminate");
                }
            }
        } catch (InterruptedException ie) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}

在上述代码中,首先创建了一个线程池并提交了 15 个任务。然后调用 shutdown() 方法,线程池进入 SHUTDOWN 状态,不再接受新任务,但会继续处理阻塞队列中的任务。awaitTermination 方法用于等待线程池中的所有任务执行完毕。

STOP 状态

当调用线程池的 shutdownNow() 方法时,线程池会从 RUNNING 或 SHUTDOWN 状态转变为 STOP 状态。在 STOP 状态下,线程池不仅不再接受新任务,还会中断正在执行的任务,并清空阻塞队列。以下是一个示例:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ShutdownNowExample {
    public static void main(String[] args) {
        BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(10);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2, // corePoolSize
                4, // maximumPoolSize
                10, // keepAliveTime
                TimeUnit.SECONDS,
                queue);

        // 提交一些任务
        for (int i = 0; i < 15; i++) {
            int taskNumber = i;
            executor.execute(() -> {
                System.out.println("Task " + taskNumber + " is running on " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }

        // 调用 shutdownNow 方法
        executor.shutdownNow();

        // 等待所有任务执行完毕
        try {
            if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                System.err.println("Pool did not terminate");
            }
        } catch (InterruptedException ie) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}

在这个示例中,调用 shutdownNow() 方法后,线程池进入 STOP 状态,正在执行的任务会被中断,阻塞队列中的任务会被丢弃。awaitTermination 方法用于等待线程池中的所有任务处理完毕(这里的任务处理完毕包括任务正常执行结束或被中断)。

TIDYING 状态

当所有任务都已终止(包括正在执行的任务被中断和阻塞队列中的任务被处理完毕),并且工作线程数为 0 时,线程池会进入 TIDYING 状态。在 TIDYING 状态下,线程池会执行一个钩子方法 terminated()。以下是一个模拟线程池进入 TIDYING 状态的示例:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class TIDYINGExample {
    public static void main(String[] args) {
        BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(10);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2, // corePoolSize
                4, // maximumPoolSize
                10, // keepAliveTime
                TimeUnit.SECONDS,
                queue) {
            @Override
            protected void terminated() {
                System.out.println("ThreadPool is in TIDYING state and terminated method is called.");
            }
        };

        // 提交一些任务
        for (int i = 0; i < 5; i++) {
            int taskNumber = i;
            executor.execute(() -> {
                System.out.println("Task " + taskNumber + " is running on " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }

        // 调用 shutdown 方法
        executor.shutdown();

        // 等待所有任务执行完毕
        try {
            if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                executor.shutdownNow();
                if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                    System.err.println("Pool did not terminate");
                }
            }
        } catch (InterruptedException ie) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}

在上述代码中,重写了 ThreadPoolExecutorterminated() 方法。当线程池进入 TIDYING 状态时,terminated() 方法会被调用,打印出相应的信息。

TERMINATED 状态

在线程池进入 TIDYING 状态后,会执行 terminated() 钩子方法。当这个方法执行完毕,线程池就进入 TERMINATED 状态,这表示线程池彻底终止。在 TERMINATED 状态下,线程池不再有任何活动,并且资源已被释放。例如,在前面 TIDYINGExample 的示例中,当 terminated() 方法执行完毕,线程池就进入了 TERMINATED 状态。

状态转换流程图

为了更直观地理解线程池状态的转换,以下是一个简单的状态转换流程图:

stateDiagram
    [*] --> RUNNING
    RUNNING --> SHUTDOWN: shutdown()
    RUNNING --> STOP: shutdownNow()
    SHUTDOWN --> TIDYING: 所有任务执行完毕且工作线程数为 0
    STOP --> TIDYING: 所有任务执行完毕且工作线程数为 0
    TIDYING --> TERMINATED: terminated() 方法执行完毕

从流程图中可以清晰地看到线程池在不同操作下的状态转换路径。

线程池状态切换在实际应用中的注意事项

  1. 任务提交与状态检查:在向线程池提交任务时,需要注意线程池的当前状态。如果线程池已经处于 SHUTDOWN 或 STOP 状态,新任务将无法被接受。因此,在提交任务前,可以通过 isShutdown()isTerminated() 方法来检查线程池状态,以避免不必要的任务提交失败。
  2. 任务中断处理:当线程池进入 STOP 状态时,正在执行的任务会被中断。在编写任务代码时,需要正确处理 InterruptedException,以确保任务能够安全地被中断。例如,可以在 catch 块中进行资源清理和状态恢复等操作。
  3. 等待线程池终止:在调用 shutdown()shutdownNow() 方法后,通常需要等待线程池中的所有任务执行完毕或处理完毕。可以使用 awaitTermination 方法来实现这一目的。在等待过程中,需要合理设置等待时间,以避免无限期等待导致程序无法正常退出。
  4. 自定义钩子方法terminated() 钩子方法提供了一个在线程池彻底终止前执行自定义操作的机会。例如,可以在这个方法中进行资源释放、日志记录等操作。在重写 terminated() 方法时,要确保操作的正确性和稳定性,以免影响线程池的正常终止。

总结线程池状态切换的要点

  1. 状态与操作对应:不同的方法调用(如 shutdown()shutdownNow())会导致线程池进入不同的状态,并且每个状态都有其特定的行为,如是否接受新任务、如何处理正在执行的任务和阻塞队列中的任务等。
  2. 原子变量 ctl:线程池状态和工作线程数通过 ctl 这个原子变量进行存储和管理,这种设计方式保证了并发操作的高效性和原子性。理解 ctl 的编码和解码方式对于深入理解线程池状态切换机制非常重要。
  3. 钩子方法与状态转换配合terminated() 钩子方法在线程池进入 TIDYING 状态后执行,为开发人员提供了在彻底终止前执行自定义逻辑的机会。合理利用这个方法可以使线程池的生命周期管理更加完善。

通过深入理解 Java 线程池的状态切换机制,开发人员能够更加有效地使用线程池,提高应用程序的性能和稳定性,避免因线程池状态管理不当而导致的各种问题。在实际应用中,根据具体的业务需求和场景,合理地控制线程池的状态转换,是实现高效并发编程的关键之一。