1. 概述
ExecutorService
是 JDK 提供的一套用于简化异步任务执行的 API。它自动管理线程池,并提供了一套接口来提交任务,使得开发者可以专注于业务逻辑而不是线程管理。
2. 创建 ExecutorService
2.1. 使用 Executors 工厂方法
最简单的创建方式是使用 Executors
类提供的静态工厂方法。
比如下面这行代码会创建一个包含 10 个线程的固定大小线程池:
ExecutorService executor = Executors.newFixedThreadPool(10);
除了这个方法外,Executors
还提供了多个工厂方法来创建适用于不同场景的线程池。建议根据具体需求查阅 Oracle 官方文档。
2.2. 直接实例化实现类
由于 ExecutorService
是一个接口,你也可以直接使用其实现类,比如 ThreadPoolExecutor
来进行更灵活的配置:
ExecutorService executorService =
new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
这段代码和 Executors.newSingleThreadExecutor()
的源码几乎一致。大多数情况下,手动配置线程池并不是必须的。
3. 向 ExecutorService 提交任务
ExecutorService
支持执行两种类型的任务:Runnable
和 Callable
。
为了演示方便,我们用 Lambda 表达式定义两个任务:
Runnable runnableTask = () -> {
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Callable<String> callableTask = () -> {
TimeUnit.MILLISECONDS.sleep(300);
return "Task's execution";
};
List<Callable<String>> callableTasks = new ArrayList<>();
callableTasks.add(callableTask);
callableTasks.add(callableTask);
callableTasks.add(callableTask);
提交任务的方式有以下几种:
execute()
:只适用于Runnable
,无返回值。submit()
:支持Runnable
和Callable
,返回Future
。invokeAny()
:并发执行多个任务,返回首个成功执行的结果。invokeAll()
:并发执行多个任务,返回所有结果。
示例:
executorService.execute(runnableTask);
Future<String> future = executorService.submit(callableTask);
String result = executorService.invokeAny(callableTasks);
List<Future<String>> futures = executorService.invokeAll(callableTasks);
4. 关闭 ExecutorService
⚠️ 注意:**ExecutorService
不会自动关闭**,即使没有任务在执行。它会持续等待新任务,导致 JVM 无法正常退出。
正确的关闭方式包括:
shutdown()
:停止接收新任务,等当前任务执行完后关闭。shutdownNow()
:立即尝试关闭,不保证所有任务都已完成。
✅ 推荐的优雅关闭方式(Oracle 官方推荐):
executorService.shutdown();
try {
if (!executorService.awaitTermination(800, TimeUnit.MILLISECONDS)) {
executorService.shutdownNow();
}
} catch (InterruptedException e) {
executorService.shutdownNow();
}
这种方式会先停止接收新任务,然后最多等待 800ms,超时则强制关闭。
5. Future 接口详解
submit()
和 invokeAll()
方法返回 Future
对象,可以用于获取任务执行结果或判断任务状态。
获取结果
Future<String> future = executorService.submit(callableTask);
String result = null;
try {
result = future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
⚠️ 注意:调用 get()
会阻塞当前线程,直到任务完成。
设置超时避免无限等待
String result = future.get(200, TimeUnit.MILLISECONDS);
如果超过 200ms 任务仍未完成,将抛出 TimeoutException
。
其他常用方法
boolean canceled = future.cancel(true);
boolean isCancelled = future.isCancelled();
boolean isDone = future.isDone();
6. ScheduledExecutorService 定时任务
ScheduledExecutorService
支持延迟执行和周期性执行任务。
创建方式
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
延迟执行
Future<String> resultFuture =
executorService.schedule(callableTask, 1, TimeUnit.SECONDS);
固定频率执行
executorService.scheduleAtFixedRate(runnableTask, 100, 450, TimeUnit.MILLISECONDS);
固定延迟执行
executorService.scheduleWithFixedDelay(task, 100, 150, TimeUnit.MILLISECONDS);
⚠️ 注意:
scheduleAtFixedRate()
是固定频率,但如果任务执行时间超过周期,会等待当前任务完成。scheduleWithFixedDelay()
是每次任务完成后固定延迟再执行下一次。
7. ExecutorService vs Fork/Join
虽然 Fork/Join 框架在 Java 7 后很流行,但并不适合所有场景。
- ✅
ExecutorService
更适合处理独立任务,比如每个请求一个线程。 - ✅ Fork/Join 适用于可递归拆分的任务,比如分治算法。
8. 常见踩坑总结
❌ 忘记关闭 ExecutorService:导致 JVM 无法退出。
❌ 线程池大小设置不当:
- 过大:浪费资源,线程频繁切换。
- 过小:任务排队,响应慢。
❌ **调用已取消任务的 get()
**:会抛出 CancellationException
。
❌ **长时间阻塞 get()
**:建议使用超时机制避免性能问题。
📌 本文代码示例可参考 GitHub 仓库。