在Java并发编程领域,线程池是一种至关重要的工具,它能显著提升应用程序的性能与资源管理效率。通过复用线程,线程池避免了频繁创建和销毁线程所带来的开销。在本教程中,我们将深入探讨Java和Guava库中线程池的使用。
一、Java中的线程池
(一)Executor框架
Java的java.util.concurrent包提供了Executor框架,这是管理线程池的核心。
Executor接口是该框架的基础,它定义了一个简单的方法execute(Runnable task),用于提交任务执行。
Executor接口本身并不直接管理线程,而是将任务的执行委托给实现类。
(二)ExecutorService
ExecutorService接口扩展了Executor接口,提供了更丰富的功能,用于管理线程池的生命周期以及任务的提交与执行。它包含了启动、关闭线程池的方法,以及提交任务并获取执行结果的方法。
2.1 创建线程池
在Java中,我们可以使用Executors类的静态方法来创建不同类型的线程池:
- FixedThreadPool:创建一个固定大小的线程池,线程池中的线程数量在创建时就被确定,并且不会改变。如果提交的任务数量超过了线程池的容量,任务将被放入队列中等待执行。
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
- CachedThreadPool:创建一个可缓存的线程池,如果线程池中的线程在一段时间内没有被使用,它们将被回收。如果提交的任务数量超过了当前线程池中的线程数量,新的线程将被创建。
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
- SingleThreadExecutor:创建一个单线程的线程池,它只使用一个线程来执行任务。所有提交的任务将按照顺序依次执行。
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
- ScheduledThreadPool:创建一个支持定时及周期性任务执行的线程池。可以安排任务在指定的延迟后执行,或者定期重复执行。
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
2.2 提交任务
一旦创建了线程池,我们可以使用submit方法提交任务。submit方法有多种重载形式,可接受Runnable或Callable任务,并返回Future对象,通过Future对象可以获取任务的执行结果。
Future<Integer> future = fixedThreadPool.submit(() -> {
// 执行任务并返回结果
return 42;
});
try {
Integer result = future.get();
System.out.println("任务执行结果: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
2.3 关闭线程池
在应用程序结束时,我们需要正确关闭线程池,以确保所有任务都能正常完成,并释放资源。ExecutorService提供了shutdown和shutdownNow方法来实现这一点。
- shutdown:启动一个有序关闭过程,不再接受新任务,但会继续执行已提交的任务。
fixedThreadPool.shutdown();
- shutdownNow:尝试停止所有正在执行的任务,停止等待任务的处理,并返回等待执行的任务列表。
List<Runnable> tasks = fixedThreadPool.shutdownNow();
(三)示例:使用线程池进行并行计算
假设我们有一个简单的任务,需要计算一组数字的平方。我们可以使用线程池来并行执行这些计算,以提高效率。
public class ThreadPoolExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
List<Future<Integer>> futures = new ArrayList<>();
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
for (int number : numbers) {
Future<Integer> future = executorService.submit(() -> number * number);
futures.add(future);
}
executorService.shutdown();
for (Future<Integer> future : futures) {
try {
System.out.println("平方结果: " + future.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
}
运行结果是:
平方结果: 1
平方结果: 4
平方结果: 9
平方结果: 16
平方结果: 25
二、Guava中的线程池
Guava库提供了ListeningExecutorService接口,它扩展了ExecutorService,并提供了更方便的异步任务处理方式。ListeningExecutorService允许我们注册监听器,以便在任务完成时得到通知。
(一)创建ListeningExecutorService
在Guava中,我们可以使用MoreExecutors类的静态方法来创建ListeningExecutorService。例如,我们可以将一个普通的ExecutorService包装成ListeningExecutorService:
ExecutorService executorService = Executors.newFixedThreadPool(5);
ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(executorService);
(二)提交任务并注册监听器
提交任务后,我们可以使用Futures.addCallback方法注册一个回调,当任务完成时,回调的onSuccess或onFailure方法将被调用。
Future<Integer> future = listeningExecutorService.submit(() -> {
// 执行任务并返回结果
return 42;
});
Futures.addCallback(future, new FutureCallback<Integer>() {
@Override
public void onSuccess(Integer result) {
System.out.println("任务成功执行,结果: " + result);
}
@Override
public void onFailure(Throwable t) {
System.out.println("任务执行失败: " + t.getMessage());
}
});
(三)示例:使用Guava线程池进行异步任务处理
以下是一个完整的示例,展示如何使用Guava的线程池进行异步任务处理,并注册监听器来处理任务结果。
public class GuavaThreadPoolExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(executorService);
ListenableFuture<Integer> future = listeningExecutorService.submit(() -> {
// 模拟任务执行
Thread.sleep(2000);
return 42;
});
final ExecutorService callbackExecutor = Executors.newFixedThreadPool(3);
Futures.addCallback(future, new FutureCallback<Integer>() {
@Override
public void onSuccess(Integer result) {
System.out.println("任务成功执行,结果: " + result);
callbackExecutor.shutdown();
}
@Override
public void onFailure(Throwable t) {
System.out.println("任务执行失败: " + t.getMessage());
callbackExecutor.shutdown();
}
}, callbackExecutor);
// 关闭线程池
executorService.shutdown();
}
}
运行结果是:
任务成功执行,结果: 42
三、补充
补充一下Executors的工厂方法:
方法 | 描述 | 适用场景 |
| 创建一个可缓存的线程池。如果线程池的当前线程数超过了处理需求,则会回收空闲线程;如果需求增加,则可以添加新线程。 | 执行大量短期异步任务 |
| 创建一个固定大小的线程池。线程池中的线程数量固定,如果所有线程都在忙,新的任务会在队列中等待。 | 负载较重且任务量稳定的场景 |
| 创建一个支持定时及周期性任务执行的线程池。可以调度命令在给定的延迟后运行,或定期执行。 | 需要定时执行任务的场景 |
| 创建一个单线程化的线程池。确保所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。 | 需要保证任务顺序执行的场景 |
| 创建一个单线程的定时任务执行器。支持定时及周期性任务执行。 | 需要单线程执行定时任务的场景 |
| 创建一个为每个任务创建新线程的执行器。每个任务都会启动一个新的线程来执行。 | 任务之间完全独立且不需要复用线程的场景 |
| 创建一个为每个任务创建虚拟线程的执行器。虚拟线程是轻量级线程,适用于高并发场景。 | 需要高并发且任务量大的场景 |
| 创建一个工作窃取线程池。使用 ForkJoinPool 实现,线程池中的线程会主动“窃取”其他线程的任务来执行,提高 CPU 利用率。 | 计算密集型任务,可以充分利用多核处理器的优势 |
文末总结
线程池是Java并发编程中的重要工具,无论是Java原生的Executor框架还是Guava库提供的扩展,都为我们提供了强大的异步任务处理能力。通过合理使用线程池,我们可以有效提高应用程序的性能和资源利用率。在实际应用中,根据具体需求选择合适的线程池类型和使用方式至关重要。