Java 面试题:从源码理解 ThreadPoolExecutor 线程池的参数和工作原理 --xunznux

ThreadPoolExecutor

如何创建线程池

  • Executors.newSingleThreadExecutor()
  • Executors.newFixedThreadPool(5)
  • Executors.newCachedThreadPool()
  • Executors.newScheduledThreadPool(5);
  • Executors.newSingleThreadScheduledExecutor();
threadPool.execute(()->{
          System.out.println(Thread.currentThread().getName()+" ok");
        });

通过 ThreadPoolExecutor 创建:

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, workQueue);

工作原理

  1. 线程池初始化:线程池在初始化时并不会立即创建 corePoolSize 数量的核心线程,而是采用惰性加载的方式,等到有任务提交后才开始创建核心线程。
  2. 核心线程创建:当线程池中的线程数量小于 corePoolSize 时,即使所有线程处于空闲状态,也会创建新的核心线程来处理新添加的任务。
  3. 任务入队:如果线程数量已达到 corePoolSize,但缓冲队列 workQueue 未满,任务会被放入缓冲队列等待处理。
  4. 非核心线程创建:当线程数量达到 corePoolSize 且缓冲队列已满,但线程数量仍小于 maximumPoolSize 时,线程池会创建新的非核心线程来处理任务。
  5. 任务处理策略:如果线程数量达到 maximumPoolSize 且缓冲队列也已满,任务将由 handler 所定义的策略进行处理。

核心源码

构造函数

/**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @param threadFactory the factory to use when the executor
     *        creates a new thread
     * @param handler the handler to use when execution is blocked
     *        because the thread bounds and queue capacities are reached
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue}
     *         or {@code threadFactory} or {@code handler} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
  • corePoolSize:线程池中的常驻核心线程数,估算平时的流量需要的线程数。0<= n <= Integer.MAX_VALUE=2147483647
  • maximumPoolSize:线程池能够容纳同时执行的最大线程数,性能最高线程数,应对突发的高峰流量。此值必须大于 0<= n <= Integer.MAX_VALUE。
  • keepAliveTime:多余的空闲线程存活时间。当前线程数量超过corePoolSize时,当空闲时间达到keepAliveTime时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止。0<= n <= Long.MAX_VALUE
  • unit:keepAliveTime的单位。纳秒、微秒、毫秒…天
  • workQueue:任务队列,被提交但尚未执行的任务,估算最大流量,合理设置阻塞队列长度
  • threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程,一般用默认的即可
  • handler:拒绝策略,表示当队列满了并且线程大于线程池的最大线程数(maximumPoolSize)

拒绝策略

/**
 * 处理被拒绝任务的策略:如果任务被拒绝,该策略会让任务在调用 `execute` 方法的线程中直接执行,
 * 除非线程池已经关闭,此时任务会被丢弃。
 */
public static class CallerRunsPolicy implements RejectedExecutionHandler {
    /**
     * 创建一个 {@code CallerRunsPolicy} 实例。
     */
    public CallerRunsPolicy() { }

    /**
     * 在调用者的线程中执行任务 r,除非线程池已经关闭,此时任务会被丢弃。
     *
     * @param r 请求执行的可运行任务
     * @param e 尝试执行此任务的线程池
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            r.run();  // 直接在调用者线程中运行任务
        }
    }
}

/**
 * 处理被拒绝任务的策略:抛出 {@code RejectedExecutionException} 异常。
 */
public static class AbortPolicy implements RejectedExecutionHandler {
    /**
     * 创建一个 {@code AbortPolicy} 实例。
     */
    public AbortPolicy() { }

    /**
     * 总是抛出 RejectedExecutionException 异常。
     *
     * @param r 请求执行的可运行任务
     * @param e 尝试执行此任务的线程池
     * @throws RejectedExecutionException 总是抛出该异常
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() +
                                             " rejected from " +
                                             e.toString());  // 抛出任务被拒绝异常
    }
}

/**
 * 处理被拒绝任务的策略:静默丢弃被拒绝的任务。
 */
public static class DiscardPolicy implements RejectedExecutionHandler {
    /**
     * 创建一个 {@code DiscardPolicy} 实例。
     */
    public DiscardPolicy() { }

    /**
     * 什么也不做,导致任务 r 被丢弃。
     *
     * @param r 请求执行的可运行任务
     * @param e 尝试执行此任务的线程池
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        // 不执行任务,任务被直接丢弃
    }
}

/**
 * 处理被拒绝任务的策略:丢弃最旧的未处理请求,然后重试执行新的任务,
 * 除非线程池已经关闭,此时任务会被丢弃。
 */
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    /**
     * 创建一个 {@code DiscardOldestPolicy} 实例。
     */
    public DiscardOldestPolicy() { }

    /**
     * 获取并忽略线程池中最旧的任务(如果有可用的任务),然后重试执行任务 r,
     * 除非线程池已关闭,此时任务 r 将被丢弃。
     *
     * @param r 请求执行的可运行任务
     * @param e 尝试执行此任务的线程池
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            e.getQueue().poll();  // 丢弃最旧的任务
            e.execute(r);  // 重试执行新的任务
        }
    }
}

解释:

  • CallerRunsPolicy:当任务被拒绝时,将任务在调用者线程中执行,避免任务丢失。
  • AbortPolicy:当任务被拒绝时,抛出异常,通知调用者任务无法执行。
  • DiscardPolicy:静默丢弃被拒绝的任务,任务被忽略。
  • DiscardOldestPolicy:丢弃队列中最旧的任务,然后尝试重新执行新的任务。

执行任务的流程(原理)

/**
 * 在将来的某个时间执行给定的任务。任务可能会在一个新线程中
 * 执行,也可能在一个已存在的线程池中的线程中执行。
 *
 * 如果任务无法提交执行,可能是因为线程池已经关闭或其容量已满,
 * 任务将由当前的 {@code RejectedExecutionHandler} 处理。
 *
 * @param command 需要执行的任务
 * @throws RejectedExecutionException 由 {@code RejectedExecutionHandler}
 *         决定,当任务无法被接受执行时抛出
 * @throws NullPointerException 如果 {@code command} 为 null
 */
public void execute(Runnable command) {
    // 如果传入的任务为 null,抛出空指针异常
    if (command == null)
        throw new NullPointerException();

    /*
     * 依次进行以下三步:
     *
     * 1. 如果运行的线程数量小于 corePoolSize,尝试启动一个新线程,
     *    并将给定的任务作为第一个任务执行。对 addWorker 的调用会
     *    原子性地检查线程池状态和工作线程数量,从而防止不必要的
     *    线程增加,如果不应该增加线程则返回 false。
     *
     * 2. 如果任务可以成功加入队列,我们仍然需要双重检查是否应该
     *    增加线程(因为在上次检查之后,可能已有线程退出)或线程池
     *    是否已关闭。所以我们重新检查状态,如果线程池已停止,则回滚
     *    入队操作,或者如果没有线程,则启动一个新线程。
     *
     * 3. 如果任务无法入队,则我们尝试添加一个新线程。如果失败,
     *    说明线程池已关闭或达到饱和状态,此时拒绝任务执行。
     */
    
    // 获取线程池的控制状态
    int c = ctl.get();
    
    // 如果当前工作线程数小于 corePoolSize,尝试添加一个新的核心线程执行任务
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true)) // 添加成功则直接返回
            return;
        // 重新获取线程池状态
        c = ctl.get();
    }
    
    // 如果线程池正在运行并且任务成功放入工作队列
    if (isRunning(c) && workQueue.offer(command)) {
        // 再次检查线程池状态
        int recheck = ctl.get();
        // 如果线程池不再运行且成功移除队列中的任务,则拒绝该任务
        if (!isRunning(recheck) && remove(command))
            reject(command);
        // 如果线程池没有任何工作线程,则添加一个新的非核心线程
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 如果任务无法放入工作队列,尝试添加新的非核心线程,失败则拒绝任务
    else if (!addWorker(command, false))
        reject(command);
}

解释:
代码检查是否需要创建新线程执行任务,首先判断当前工作线程数是否小于核心线程数 corePoolSize,如果是,则尝试创建新的核心线程。
如果不能创建新线程,则尝试将任务放入队列中,并在任务入队后再次检查线程池状态,确保线程池仍在运行且有足够的线程处理任务。
如果任务既不能入队也不能创建新的线程,则最终通过拒绝策略处理任务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值