1. 线程池的概念
线程池是一种管理线程的机制,通过复用线程池中的线程来执行多个任务,减少创建和销毁线程所带来的性能开销。线程池在应用程序启动时创建多个线程,任务提交到线程池后,线程池中的线程会自动执行这些任务。当任务完成后,线程不会被销毁,而是继续等待下一个任务。
2. 为什么要使用线程池?
线程池具有以下优点:
- 降低资源消耗:通过重复利用线程,避免了频繁创建和销毁线程所带来的性能开销。
- 提高响应速度:线程池中的线程已创建并准备好执行任务,从而缩短了任务启动时间。
- 提高线程的可管理性:线程池可以限制线程的数量,避免线程数量过多导致的资源耗尽问题。
- 提供更强大的功能:线程池提供了定时执行、定期执行等高级功能。
3. Java中的线程池实现
Java通过java.util.concurrent
包中的Executor
接口和ThreadPoolExecutor
类实现了线程池。Executor
接口定义了一个执行任务的方法,而ThreadPoolExecutor
是Executor
的一个具体实现,提供了丰富的线程池配置选项。
4. Executor框架
Executor框架是Java提供的一套线程池解决方案,提供了简单易用的API来执行异步任务。Executor框架的核心接口是Executor
和ExecutorService
,分别定义了线程池的基本操作和生命周期管理。Executors
类提供了创建和配置线程池的工厂方法。
5.Executor框架的使用
使用Executor框架创建线程池的基本步骤如下:
- 创建线程池:通过Executors类的工厂方法创建线程池,如newFixedThreadPool、newCachedThreadPool、newScheduledThreadPool等。
- 提交任务:使用ExecutorService的submit()或execute()方法提交任务。
- 关闭线程池:使用ExecutorService的shutdown()方法关闭线程池。
示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorExample {
public static void main(String[] args) {
// 创建一个拥有5个线程的FixedThreadPool
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
// 提交10个任务到线程池
for (int i = 0; i < 10; i++) {
final int taskId = i + 1;
fixedThreadPool.submit(() -> {
System.out.println("Task " + taskId + " is being executed by thread " + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
fixedThreadPool.shutdown();
}
}
6. 常用的线程池类型
Java提供了几种预定义的线程池,可以通过Executors
类的工厂方法创建:
- FixedThreadPool:固定数量的线程池。
- CachedThreadPool:缓存线程池,根据需要创建新线程,空闲线程会被回收。
- SingleThreadExecutor:单线程的线程池,所有任务按顺序执行。
- ScheduledThreadPool:支持定时和周期性任务的线程池。
- FixedThreadPool:固定大小线程池 FixedThreadPool是一个拥有固定线程数量的线程池。当任务到来时,若线程池中有空闲线程,则立即执行;若没有空闲线程,则任务进入等待队列,等待其他任务完成后执行。FixedThreadPool适用于处理需要并发执行的任务数量有限的场景。
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
- CachedThreadPool:缓存线程池
CachedThreadPool是一个可以缓存线程的线程池。当任务到来时,若线程池中有空闲线程,则立即执行;若没有空闲线程且线程池未达到最大容量,则创建新线程执行。CachedThreadPool适用于执行大量短时任务的场景。
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
- ScheduledThreadPool:定时线程池
ScheduledThreadPool是一个可以执行定时任务和周期性任务的线程池。它提供了类似于Timer的功能,但拥有更高的性能。ScheduledThreadPool适用于需要执行定时任务和周期性任务的场景。
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
- SingleThreadExecutor:单线程池
SingleThreadExecutor是一个只有一个线程的线程池。它可以保证提交的任务按顺序执行,适用于需要顺序执行任务的场景。
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
7. 线程池的重要参数:
- 核心线程数(corePoolSize):线程池中始终保持的线程数量,即使这些线程处于空闲状态。
- 最大线程数(maximumPoolSize):线程池允许的最大线程数量。
- 线程空闲时间(keepAliveTime):当线程池中线程数量超过核心线程数时,空闲线程在被回收前可以保持的最长时间。
- 时间单位(timeUnit):线程空闲时间的单位。
- 等待队列(workQueue):存储等待执行的任务的队列。
- 线程工厂(threadFactory):用于创建线程的工厂,可以自定义线程的属性,如名称、优先级等。
- 拒绝策略(rejectedExecutionHandler):当线程池无法处理新任务时所采取的措施。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class CustomThreadPoolExample {
public static void main(String[] args) {
ThreadFactory customThreadFactory = new ThreadFactory() {
private final AtomicInteger threadNumber = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "CustomThread-" + threadNumber.getAndIncrement());
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
};
ThreadPoolExecutor customThreadPool = new ThreadPoolExecutor(
2, // 核心线程数
4, // 最大线程数
60, // 线程空闲时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(10), // 等待队列
customThreadFactory, // 自定义线程工厂
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
// 提交任务到自定义线程池
for (int i = 0; i < 20; i++) {
final int taskId = i;
customThreadPool.execute(() -> {
System.out.println("Task " + taskId + " is executed by thread " + Thread.currentThread().getName());
try {
Thread.sleep(1000); // 模拟任务执行时间
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
customThreadPool.shutdown();
}
}
8. 线程池的拒绝策略 Java提供了四种预定义的拒绝策略:
线程池的拒绝策略(RejectedExecutionHandler)是当线程池无法处理新提交的任务时所采取的措施。线程池可能因为达到最大线程数、等待队列已满等原因而无法处理新任务。
- AbortPolicy:抛出RejectedExecutionException异常。
- CallerRunsPolicy:提交任务的线程自己执行任务。
- DiscardPolicy:直接丢弃任务。
- DiscardOldestPolicy:丢弃等待队列中最旧的任务,然后尝试重新提交当前任务。 你还可以实现RejectedExecutionHandler接口,自定义拒绝策略。
- AbortPolicy(默认策略)
AbortPolicy策略会抛出一个未检查的RejectedExecutionException异常,表示任务无法被执行。这种策略直接告知调用者任务无法执行,但可能导致调用者程序终止。
RejectedExecutionHandler abortPolicy = new ThreadPoolExecutor.AbortPolicy();
- CallerRunsPolicy
CallerRunsPolicy策略使得提交任务的线程自己去执行该任务。这种策略降低了任务在新线程中执行的并发性,但可以有效降低任务提交速度,从而使得线程池有更多空闲时间处理等待队列中的任务。
RejectedExecutionHandler callerRunsPolicy = new ThreadPoolExecutor.CallerRunsPolicy();
- DiscardPolicy
DiscardPolicy策略直接丢弃无法执行的任务,不会给调用者任何反馈。这种策略适用于可以容忍任务丢失的场景。
RejectedExecutionHandler discardPolicy = new ThreadPoolExecutor.DiscardPolicy();
- DiscardOldestPolicy
DiscardOldestPolicy策略丢弃等待队列中最旧的任务,然后尝试重新提交当前任务。这种策略适用于希望优先执行新任务的场景。
RejectedExecutionHandler discardOldestPolicy = new ThreadPoolExecutor.DiscardOldestPolicy();
除了使用预定义的拒绝策略外,你还可以实现RejectedExecutionHandler接口,自定义拒绝策略。以下是一个简单的自定义拒绝策略示例:
class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("Custom rejection policy: Task " + r.toString() + " is rejected.");
}
}
在创建ThreadPoolExecutor时,可以通过设置RejectedExecutionHandler参数来指定拒绝策略:
ThreadPoolExecutor customThreadPool = new ThreadPoolExecutor(
2, // 核心线程数
4, // 最大线程数
60, // 线程空闲时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(10), // 等待队列
new CustomRejectedExecutionHandler() // 自定义拒绝策略
);
面试题
1. 什么是线程池?为什么需要使用线程池?
线程池是一种管理线程的机制,它维护一组线程,当需要执行任务时,会从线程池中分配一个空闲线程来执行任务。线程池可以有效控制线程数量,避免线程过多导致的资源消耗和系统崩溃。使用线程池可以减少线程创建和销毁的开销,提高系统性能。
2. 请简述Java中的Executor框架。
Executor框架是Java提供的一套线程池管理和任务调度的API,它位于java.util.concurrent包中。主要包括Executor接口、ThreadPoolExecutor类、Executors工具类以及一些线程池相关的辅助类。Executor框架使得线程池的创建和管理更加简单和灵活。
3. Java中有哪些常用的线程池?
Java提供了以下四种常用的线程池:
- FixedThreadPool:固定大小的线程池,所有线程都是核心线程,不会被回收。
- CachedThreadPool:可缓存的线程池,线程数量不固定,空闲线程可被回收。
- ScheduledThreadPool:定时任务线程池,用于执行定时或周期性任务。
- SingleThreadExecutor:单线程线程池,只有一个线程,任务按顺序执行。
4. 请列举创建线程池时的一些重要参数。
创建线程池时的一些重要参数包括:
- 核心线程数(corePoolSize):线程池中始终保持的线程数量。
- 最大线程数(maximumPoolSize):线程池允许的最大线程数量。
- 线程空闲时间(keepAliveTime):空闲线程在被回收前可以保持的最长时间。
- 时间单位(timeUnit):线程空闲时间的单位。
- 等待队列(workQueue):存储等待执行的任务的队列。
- 线程工厂(threadFactory):用于创建线程的工厂,可以自定义线程的属性,如名称、优先级等。
- 拒绝策略(rejectedExecutionHandler):当线程池无法处理新任务时所采取的措施。
5. 线程池中有哪些拒绝策略?
Java提供了四种预定义的拒绝策略:
- AbortPolicy:抛出RejectedExecutionException异常。
- CallerRunsPolicy:提交任务的线程自己执行任务。
- DiscardPolicy:直接丢弃任务。
- DiscardOldestPolicy:丢弃等待队列中最旧的任务,然后尝试重新提交当前任务。
6. 如何在Java中优雅地关闭线程池?
在Java中,可以使用shutdown()
和shutdownNow()
方法来关闭线程池。shutdown()
方法会等待正在执行的任务完成和等待队列中的任务执行完毕后,才会关闭线程池。而shutdownNow()
方法会尝试立即停止正在执行的任务,并返回等待队列中尚未开始执行的任务列表。通常,我们使用shutdown()
方法来优雅地关闭线程池。
7. 为什么要避免使用Executors类创建的默认线程池?
虽然Executors类提供了创建线程池的便捷方法,但它们的配置可能导致系统资源耗尽。例如,newCachedThreadPool
创建的线程池允许无限制地创建新线程,这可能导致过多的线程消耗系统资源,进而影响系统性能。newFixedThreadPool
和newSingleThreadExecutor
创建的线程池使用无界的等待队列,这可能导致队列无限制地增长,消耗内存资源。因此,通常建议创建自定义线程池,以便更好地控制线程池的各项参数。
8. 在Java中如何处理线程池中的异常?
要处理线程池中的异常,可以使用Future
来接收线程池执行任务的结果。当调用Future.get()
方法时,如果任务执行过程中抛出了异常,那么get()
方法将抛出ExecutionException
异常。可以通过捕获该异常并调用getCause()
方法来获取原始异常信息。
另一种方法是实现Thread.UncaughtExceptionHandler
接口,为线程设置一个未捕获异常处理器。在自定义的线程工厂中创建线程时,为线程设置该处理器。这样,当线程执行过程中抛出异常时,未捕获异常处理器的uncaughtException()
方法将被调用。
9. 如何实现一个延迟任务或定时任务?
要实现延迟任务或定时任务,可以使用ScheduledThreadPoolExecutor
类。它是ThreadPoolExecutor
的一个子类,提供了任务调度功能。
可以使用Executors.newScheduledThreadPool
方法创建一个定时任务线程池。然后,可以使用schedule
方法来安排一个延迟任务,或使用scheduleAtFixedRate
和scheduleWithFixedDelay
方法来安排定时任务。例如:
// 创建一个具有2个线程的定时任务线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
// 提交一个延迟3秒执行的任务
scheduledExecutorService.schedule(new RunnableTask(), 3, TimeUnit.SECONDS);
// 提交一个初始延迟1秒,每隔2秒执行一次的任务
scheduledExecutorService.scheduleAtFixedRate(new RunnableTask(), 1, 2, TimeUnit.SECONDS);
// 提交一个初始延迟1秒,每次任务执行完毕后等待2秒再执行下一次任务的任务
scheduledExecutorService.scheduleWithFixedDelay(new RunnableTask(), 1, 2, TimeUnit.SECONDS);
10. 什么是Java中的CompletableFuture?
CompletableFuture
是Java 8引入的一种异步编程工具,位于java.util.concurrent
包中。它实现了Future
和CompletionStage
接口,可以用于表示异步计算的结果。与Future
相比,CompletableFuture
提供了更多的功能,如组合多个异步任务、异常处理、任务完成时的回调等。
要使用CompletableFuture
,可以使用supplyAsync
、runAsync
等方法创建一个新的CompletableFuture
实例,并将要执行的任务传入。然后,可以使用thenApply
、thenAccept
、thenRun
等方法来定义任务完成时的回调,或使用exceptionally
方法来处理异常。例如:
CompletableFuture.supplyAsync(() -> {
// 异步执行的任务
return "Hello, world!";
}).thenAccept(result -> {
// 任务完成时的回调
System.out.println(result);
}).exceptionally(ex -> { // 异常处理 System.out.println("Error: " + ex.getMessage()); return null; });
CompletableFuture还提供了一些静态方法,如
allOf和
anyOf,用于组合多个
CompletableFuture`实例。例如:
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
// 等待所有任务完成
CompletableFuture<Void> allFutures =CompletableFuture.allOf(future1, future2);
// 等待任何一个任务完成
CompletableFuture<Object> anyFutures = CompletableFuture.anyOf(future1, future2);
// 获取任务结果
String result1 = future1.join();
String result2 = future2.join();
通过CompletableFuture
,我们可以更方便地实现异步任务的编排和组合,提高程序的响应性和性能。