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

👉博主介绍: 博主从事应用安全和大数据领域,有8年研发经验,5年面试官经验,Java技术专家,WEB架构师,阿里云专家博主,华为云云享专家,51CTO 专家博主

⛪️ 个人社区:个人社区
💞 个人主页:个人主页
🙉 专栏地址: ✅ Java 中级
🙉八股文专题:剑指大厂,手撕 Java 八股文

在这里插入图片描述

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:尝试停止所有正在执行的任务,并返回等待执行的任务列表。

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

精彩专栏推荐订阅:在下方专栏👇🏻
2023年华为OD机试真题(A卷&B卷)+ 面试指导
精选100套 Java 项目案例
面试需要避开的坑(活动)
你找不到的核心代码
带你手撕 Spring
Java 初阶

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

激流丶

感觉小弟写的不错,给点鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值