【阿里一面】你知道线程池的执行原理吗?如何优雅的关闭呢?

76 阅读4分钟

1. 线程池的执行流程和原理

线程池的执行流程

  1. 提交任务:客户端通过 executesubmit 方法将任务提交到线程池。
  2. 核心线程:如果当前运行的线程数小于核心线程数(corePoolSize),线程池会创建新的线程来执行任务,即使其他空闲的核心线程也可以执行新任务。
  3. 工作队列:如果当前运行的线程数已经达到核心线程数,任务会被放入工作队列(workQueue)中等待执行。
  4. 最大线程:如果工作队列已满且当前运行的线程数小于最大线程数(maximumPoolSize),线程池会创建新的线程来执行任务。
  5. 拒绝策略:如果当前运行的线程数已经达到最大线程数且工作队列已满,线程池会根据拒绝策略处理新提交的任务。

线程池的原理

  • 线程复用:线程池通过复用已创建的线程来执行新任务,减少了线程创建和销毁的开销。
  • 资源控制:通过设置核心线程数、最大线程数和工作队列大小,可以有效控制资源的使用。
  • 任务调度:线程池可以根据任务的优先级和类型进行调度,提高系统的响应速度和吞吐量。

2. 线程池的拒绝策略

线程池的拒绝策略决定了当线程池无法接受新任务时的行为。Java 提供了以下几种拒绝策略:

  1. AbortPolicy:默认策略,抛出 RejectedExecutionException 异常。
  2. CallerRunsPolicy:由调用线程(提交任务的线程)执行该任务,适用于任务不能丢弃的情况。
  3. DiscardPolicy:直接丢弃任务,不抛出异常。
  4. DiscardOldestPolicy:丢弃工作队列中最老的任务,然后尝试重新提交当前任务。

3. 如何确定线程池的核心线程数

确定线程池的核心线程数需要考虑以下几个因素:

  1. CPU 核心数:对于 CPU 密集型任务,核心线程数通常设置为 CPU 核心数 + 1,以充分利用 CPU 资源。
  2. IO 密集型任务:对于 IO 密集型任务,核心线程数可以设置为 CPU 核心数 * 2 或更高,因为 IO 密集型任务大部分时间都在等待 IO 操作完成。
  3. 任务类型:根据任务的性质和系统负载情况,适当调整核心线程数。
  4. 系统资源:考虑系统内存和 CPU 资源的限制,避免过度占用资源导致系统性能下降。

4. 如何优雅的关闭线程池

优雅地关闭线程池意味着在关闭线程池之前,确保所有已提交的任务都已完成。Java 提供了以下方法来优雅地关闭线程池:

  1. shutdown:停止接收新任务,但允许已提交的任务继续执行,直到所有任务完成。
  2. awaitTermination:等待所有任务完成,超时后返回。
  3. shutdownNow:尝试停止所有正在执行的任务,并返回等待执行的任务列表。

5. 用 Java 实现优雅的关闭线程池

如何优雅地关闭线程池:

import java.util.concurrent.*;

public class ThreadPoolExample {

    public static void main(String[] args) {
        // 创建线程池
        ExecutorService executorService = new ThreadPoolExecutor(
            4, // 核心线程数
            10, // 最大线程数
            60, // 空闲线程存活时间
            TimeUnit.SECONDS, // 时间单位
            new LinkedBlockingQueue<>(100), // 工作队列
            new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
        );

        // 提交任务
        for (int i = 0; i < 150; i++) {
            final int taskId = i;
            executorService.submit(() -> {
                System.out.println("Task " + taskId + " is running by " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000); // 模拟任务执行时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Task " + taskId + " is completed by " + Thread.currentThread().getName());
            });
        }

        // 优雅地关闭线程池
        shutdownAndAwaitTermination(executorService);
    }

    public static void shutdownAndAwaitTermination(ExecutorService pool) {
        pool.shutdown(); // 不再接受新任务
        try {
            // 等待所有任务完成,最多等待 60 秒
            if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
                pool.shutdownNow(); // 尝试停止所有正在执行的任务
                // 再次等待所有任务完成,最多等待 10 秒
                if (!pool.awaitTermination(10, TimeUnit.SECONDS))
                    System.err.println("Pool did not terminate");
            }
        } catch (InterruptedException ie) {
            pool.shutdownNow(); // (Re-)Cancel if current thread also interrupted
            Thread.currentThread().interrupt(); // Preserve interrupt status
        }
    }
}
  1. 创建线程池:使用 ThreadPoolExecutor 创建一个线程池,指定核心线程数、最大线程数、空闲线程存活时间、工作队列和拒绝策略。
  2. 提交任务:通过 executorService.submit 方法提交多个任务。
  3. 优雅地关闭线程池
    • shutdown:停止接收新任务,但允许已提交的任务继续执行。
    • awaitTermination:等待所有任务完成,超时后返回。
    • shutdownNow:尝试停止所有正在执行的任务,并返回等待执行的任务列表。

通过这种方式,可以确保在关闭线程池之前,所有已提交的任务都能完成,从而实现优雅的关闭。