MST

星途 面试题库

面试题:Java Executors框架自定义实现及原理深度剖析

请基于Java的`Executor`、`ExecutorService`接口,自定义实现一个线程池,要求实现任务队列管理、线程复用、线程生命周期控制等功能,并详细解释你实现过程中对`RejectedExecutionHandler`拒绝策略的应用及原理,以及在不同负载情况下线程池的工作机制。
43.9万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

自定义线程池实现

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

public class CustomThreadPool {
    private static final int CORE_POOL_SIZE = 5;
    private static final int MAX_POOL_SIZE = 10;
    private static final long KEEP_ALIVE_TIME = 10;
    private static final TimeUnit TIME_UNIT = TimeUnit.SECONDS;
    private static final BlockingQueue<Runnable> WORK_QUEUE = new LinkedBlockingQueue<>(20);
    private static final ThreadFactory THREAD_FACTORY = Executors.defaultThreadFactory();
    private static final RejectedExecutionHandler HANDLER = new ThreadPoolExecutor.AbortPolicy();

    private static ThreadPoolExecutor executor;

    static {
        executor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                KEEP_ALIVE_TIME,
                TIME_UNIT,
                WORK_QUEUE,
                THREAD_FACTORY,
                HANDLER);
    }

    public static void executeTask(Runnable task) {
        executor.execute(task);
    }

    public static void 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();
        }
    }
}

拒绝策略的应用及原理

  1. AbortPolicy(默认):当任务提交到线程池时,如果线程池队列已满且线程数达到最大线程数,AbortPolicy 会抛出 RejectedExecutionException 异常,拒绝接受新任务。它的原理是直接在 RejectedExecutionHandlerrejectedExecution 方法中抛出异常,让调用者知道任务被拒绝。
  2. CallerRunsPolicy:当任务被拒绝时,该策略会让调用者线程(即提交任务的线程)来执行被拒绝的任务。这样做的好处是不会丢失任务,但会影响调用者线程的性能。它的实现是在 rejectedExecution 方法中直接执行任务 r.run()
  3. DiscardPolicy:此策略会直接丢弃被拒绝的任务,不做任何处理。当应用程序可以承受丢失任务的后果时,可以使用此策略。rejectedExecution 方法为空实现。
  4. DiscardOldestPolicy:该策略会丢弃队列中最老的任务(即队列头部的任务),然后尝试重新提交当前被拒绝的任务。如果队列再次满了,会再次触发拒绝策略。它的原理是先移除队列头部任务 workQueue.poll(),然后重新尝试提交任务 execute(r)

不同负载情况下线程池的工作机制

  1. 低负载:当任务提交频率较低,任务数量小于核心线程数时,线程池会创建新线程来处理任务,每个任务对应一个线程。任务执行完毕后,线程不会立即销毁,而是保持存活,等待新任务到来,实现线程复用。
  2. 中等负载:随着任务提交频率增加,任务数量超过核心线程数但未达到队列容量时,新任务会被放入任务队列 WORK_QUEUE 中等待执行。核心线程处理完当前任务后,会从队列中取出新任务继续执行。
  3. 高负载:当任务数量超过队列容量且线程数未达到最大线程数时,线程池会创建新线程(最大到 MAX_POOL_SIZE)来处理任务,以提高处理能力。若任务数量持续增加,达到最大线程数且队列已满,此时会根据设置的拒绝策略处理新提交的任务。
  4. 负载降低:当任务执行完毕,线程数超过核心线程数且在 KEEP_ALIVE_TIME 内没有新任务时,多余的线程会被销毁,线程数逐渐恢复到核心线程数,以节省资源。