线程池与 Future:异步任务提交与结果获取
线程池与 Future:异步任务提交与结果获取
在现代应用程序中,直接频繁地创建和销毁线程往往效率低下且难以管理。线程池提供了一种线程复用机制,而 Future 则允许我们以异步的方式获取任务执行结果。本教程将带你从基础概念入手,逐步掌握线程池的创建、任务提交以及通过 Future 获取结果的完整流程。
为什么需要线程池
- 降低资源消耗:重复利用已创建的线程,避免频繁创建/销毁带来的开销。
- 提高响应速度:任务到达时可直接由空闲线程执行,无需等待新线程创建。
- 统一管理线程:线程池可以对线程的并发数量、执行策略等进行集中管控,避免无限制创建线程导致系统资源耗尽。
Java 通过 java.util.concurrent 包提供了强大的线程池支持,核心接口是 ExecutorService,常用实现类为 ThreadPoolExecutor 及其工厂方法类 Executors。
创建线程池
推荐使用 ThreadPoolExecutor 构造方法或 Executors 工厂类创建线程池。对于初学者,可以从 Executors 的几个快捷方法开始,但在生产环境中建议直接使用 ThreadPoolExecutor 来明确参数。
使用 Executors 工厂方法
// 固定大小的线程池
ExecutorService fixedPool = Executors.newFixedThreadPool(5);
// 单线程化的线程池,可保证任务顺序执行
ExecutorService singlePool = Executors.newSingleThreadExecutor();
// 可缓存的线程池,空闲线程会被回收,线程数可无限扩大
ExecutorService cachedPool = Executors.newCachedThreadPool();
使用 ThreadPoolExecutor 自定义参数
为获得更精细的控制,可以直接实例化 ThreadPoolExecutor:
int corePoolSize = 5; // 核心线程数
int maxPoolSize = 10; // 最大线程数
long keepAliveTime = 60L; // 空闲线程存活时间
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(100);
ExecutorService pool = new ThreadPoolExecutor(
corePoolSize,
maxPoolSize,
keepAliveTime,
unit,
workQueue
);
提交任务并获得 Future
向线程池提交任务有两种主要方式:提交 Runnable 和 Callable。
Runnable任务没有返回值,提交后返回的Future<?>仅能判断任务是否完成或取消。Callable任务可以返回计算结果,并能抛出受检异常。
提交 Runnable
Future<?> future = pool.submit(() -> {
System.out.println("执行无返回值的任务");
});
提交 Callable
Future<String> future = pool.submit(() -> {
// 模拟耗时操作
Thread.sleep(1000);
return "任务结果";
});
submit() 方法会立即返回一个 Future 对象,而任务会在线程池中的某个线程异步执行。
从 Future 获取结果
Future 接口提供了检查和等待异步执行结果的方法:
| 方法 | 说明 |
|---|---|
get() |
阻塞等待直到任务完成,并获取结果。 |
get(long timeout, TimeUnit unit) |
在指定时间内阻塞等待,超时抛出 TimeoutException。 |
isDone() |
任务是否完成(正常结束、异常、取消)均返回 true。 |
cancel(boolean mayInterruptIfRunning) |
尝试取消任务。 |
isCancelled() |
任务是否被取消。 |
示例:获取结果并处理异常
Future<Integer> future = pool.submit(() -> {
int result = 10 / 0; // 故意抛出异常
return result;
});
try {
Integer value = future.get(); // 这里会抛出 ExecutionException
System.out.println("结果:" + value);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 重置中断标志
} catch (ExecutionException e) {
Throwable cause = e.getCause(); // 获取任务中真正的异常
System.err.println("任务执行异常:" + cause.getMessage());
}
带超时的获取
try {
String result = future.get(2, TimeUnit.SECONDS);
System.out.println("结果:" + result);
} catch (TimeoutException e) {
System.out.println("任务在2秒内未完成");
future.cancel(true); // 可尝试取消
}
Future 的局限性及 CompletableFuture 简介
Future 仅提供基本的异步等待和结果获取,不支持链式回调、组合多个异步操作等高级场景。Java 8 引入了 CompletableFuture,它实现了 Future 和 CompletionStage 接口,可以灵活地组合异步逻辑。
CompletableFuture.supplyAsync(() -> {
// 异步计算
return "Hello";
}).thenApply(result -> {
// 处理上一步结果
return result + " World";
}).thenAccept(System.out::println);
虽然 CompletableFuture 功能更丰富,但理解基础的线程池与 Future 机制是掌握异步编程的关键第一步。
线程池的正确关闭
线程池使用完毕后必须关闭,否则 JVM 不会退出。关闭有两种方式:
shutdown():平滑关闭,不再接受新任务,等待已提交任务执行完成。shutdownNow():尝试立即停止所有正在执行的任务,并返回等待执行的任务列表。
pool.shutdown();
try {
if (!pool.awaitTermination(5, TimeUnit.SECONDS)) {
pool.shutdownNow();
}
} catch (InterruptedException e) {
pool.shutdownNow();
Thread.currentThread().interrupt();
}
小结
- 线程池通过线程复用和统一管理,大幅提升多线程编程的稳定性和效率。
submit()方法接受Runnable或Callable,并返回Future对象。Future用于查询任务状态、取消任务和获取结果(阻塞或超时)。- 在复杂异步场景下,可进一步学习
CompletableFuture以享受更灵活的编程模型。
现在你可以动手尝试编写一个简单的线程池示例,并利用 Future 异步获取计算结果了。