面试题答案
一键面试线程复用实现
- 核心线程与工作队列:在Java的
Executors
框架中,线程池维护了一定数量的核心线程(corePoolSize
)。当提交任务时,首先会创建核心线程来执行任务,直到核心线程数达到corePoolSize
。之后新的任务会被放入工作队列(workQueue
)中。 - 非核心线程:如果工作队列已满,且线程池中的线程数小于最大线程数(
maximumPoolSize
),则会创建非核心线程来执行任务。 - 线程复用机制:当一个线程完成任务后,它不会立即销毁,而是从工作队列中获取新的任务继续执行。这就实现了线程的复用,避免了频繁创建和销毁线程带来的开销。例如,假设有一个固定大小的线程池,核心线程数和最大线程数都为3,工作队列大小为5。开始时提交3个任务,会创建3个线程执行。当这3个任务完成后,若又提交新任务,线程池中的线程会从工作队列中获取新任务继续执行,而不是重新创建线程。
不同饱和策略处理新提交任务方式及举例
- 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个任务时,由于线程池饱和,该任务会被直接丢弃,不会执行也不会抛出异常。