MST

星途 面试题库

面试题:Java Executors任务执行流程中的线程复用及饱和策略

在Java Executors任务执行流程中,线程复用是如何实现的?当线程池达到饱和状态时,不同的饱和策略(如AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy)分别是如何处理新提交的任务的?请举例说明。
12.7万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

线程复用实现

  1. 核心线程与工作队列:在Java的Executors框架中,线程池维护了一定数量的核心线程(corePoolSize)。当提交任务时,首先会创建核心线程来执行任务,直到核心线程数达到corePoolSize。之后新的任务会被放入工作队列(workQueue)中。
  2. 非核心线程:如果工作队列已满,且线程池中的线程数小于最大线程数(maximumPoolSize),则会创建非核心线程来执行任务。
  3. 线程复用机制:当一个线程完成任务后,它不会立即销毁,而是从工作队列中获取新的任务继续执行。这就实现了线程的复用,避免了频繁创建和销毁线程带来的开销。例如,假设有一个固定大小的线程池,核心线程数和最大线程数都为3,工作队列大小为5。开始时提交3个任务,会创建3个线程执行。当这3个任务完成后,若又提交新任务,线程池中的线程会从工作队列中获取新任务继续执行,而不是重新创建线程。

不同饱和策略处理新提交任务方式及举例

  1. AbortPolicy(默认策略)
    • 处理方式:当线程池达到饱和状态(工作队列已满且线程数达到maximumPoolSize)时,新提交的任务会抛出RejectedExecutionException异常。
    • 举例
import java.util.concurrent.*;

public class AbortPolicyExample {
    public static void main(String[] args) {
        ExecutorService executorService = new ThreadPoolExecutor(
                2,
                2,
                0L,
                TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(1),
                new ThreadPoolExecutor.AbortPolicy());
        for (int i = 0; i < 4; i++) {
            int taskNumber = i;
            executorService.submit(() -> {
                System.out.println("Task " + taskNumber + " is running on thread " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        executorService.shutdown();
    }
}

在上述代码中,线程池核心线程数和最大线程数都为2,工作队列大小为1。当提交第4个任务时,会抛出RejectedExecutionException异常。 2. CallerRunsPolicy

  • 处理方式:当线程池达到饱和状态时,新提交的任务不会被丢弃,也不会抛出异常,而是由提交任务的线程(调用者线程)来执行该任务。
  • 举例
import java.util.concurrent.*;

public class CallerRunsPolicyExample {
    public static void main(String[] args) {
        ExecutorService executorService = new ThreadPoolExecutor(
                2,
                2,
                0L,
                TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(1),
                new ThreadPoolExecutor.CallerRunsPolicy());
        for (int i = 0; i < 4; i++) {
            int taskNumber = i;
            executorService.submit(() -> {
                System.out.println("Task " + taskNumber + " is running on thread " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        executorService.shutdown();
    }
}

在这个例子中,当线程池饱和时,第4个任务会由主线程(调用者线程)来执行。 3. DiscardOldestPolicy

  • 处理方式:当线程池达到饱和状态时,会丢弃工作队列中最老的一个任务(即等待时间最长的任务),然后尝试提交新任务。
  • 举例
import java.util.concurrent.*;

public class DiscardOldestPolicyExample {
    public static void main(String[] args) {
        ExecutorService executorService = new ThreadPoolExecutor(
                2,
                2,
                0L,
                TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(2),
                new ThreadPoolExecutor.DiscardOldestPolicy());
        for (int i = 0; i < 5; i++) {
            int taskNumber = i;
            executorService.submit(() -> {
                System.out.println("Task " + taskNumber + " is running on thread " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        executorService.shutdown();
    }
}

在上述代码中,线程池核心线程数和最大线程数都为2,工作队列大小为2。当提交第5个任务时,工作队列中最早的任务会被丢弃,然后第5个任务被尝试提交执行。 4. DiscardPolicy

  • 处理方式:当线程池达到饱和状态时,直接丢弃新提交的任务,不做任何处理。
  • 举例
import java.util.concurrent.*;

public class DiscardPolicyExample {
    public static void main(String[] args) {
        ExecutorService executorService = new ThreadPoolExecutor(
                2,
                2,
                0L,
                TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(1),
                new ThreadPoolExecutor.DiscardPolicy());
        for (int i = 0; i < 4; i++) {
            int taskNumber = i;
            executorService.submit(() -> {
                System.out.println("Task " + taskNumber + " is running on thread " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        executorService.shutdown();
    }
}

在这个例子中,当提交第4个任务时,由于线程池饱和,该任务会被直接丢弃,不会执行也不会抛出异常。