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 支持执行两种类型的任务:RunnableCallable

为了演示方便,我们用 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():支持 RunnableCallable,返回 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 仓库


原始标题:A Guide to the Java ExecutorService | Baeldung

« 上一篇: Java周报,133期