开发技能Java线程并发ExecutorService

ExecutorService 是 Java 并发包(java.util.concurrent)中提供的一个线程池框架接口。它封装了线程的创建、管理、复用以及任务提交等细节,让开发者专注于任务本身,而不是底层线程的生命周期。

1. 为什么需要 ExecutorService?

直接手动创建 Thread 对象存在诸多问题:

  • 资源消耗高:每个线程都需要分配栈内存(通常 1MB 左右),创建和销毁开销大。
  • 不可控:无限制创建线程会导致系统崩溃。
  • 管理困难:无法统一监控、限制并发数、排队任务。

ExecutorService 通过线程池解决了这些问题:

  • 复用线程,降低创建/销毁成本。
  • 限制最大线程数,防止资源耗尽。
  • 提供任务队列,实现提交与执行的解耦。
  • 统一管理线程生命周期(关闭、监控等)。

2. 核心功能

2.1 提交任务

  • execute(Runnable):无返回值,执行 Runnable 任务。
  • submit(Callable) / submit(Runnable):返回 Future 对象,可获取执行结果或异常。
  • invokeAll(...) / invokeAny(...):批量提交任务。

2.2 关闭线程池

  • shutdown():平滑关闭,不再接收新任务,等待已提交任务执行完。
  • shutdownNow():尝试立即停止所有正在执行的任务,并返回未开始的任务列表。
  • awaitTermination(timeout, unit):阻塞等待线程池完全终止。

2.3 监控与状态

  • isShutdown() / isTerminated():判断关闭或终止状态。

3. 创建 ExecutorService 的常用工厂方法(通过 Executors 工具类)

方法说明适用场景
newFixedThreadPool(int n)固定数量的核心线程,任务队列无界负载较重但可控的服务器
newCachedThreadPool()核心线程数 0,最大线程数很大,空闲线程存活 60 秒短期异步任务,并发量不定
newSingleThreadExecutor()单线程池,保证任务顺序执行需要顺序处理或单后台线程
newScheduledThreadPool(int n)支持定时、延迟执行任务周期性的后台任务

注意Executors 提供的某些线程池可能使用无界队列(如 FixedThreadPool),在高负载下可能导致内存溢出。在生产环境中,更推荐直接使用 ThreadPoolExecutor 构造方法显式指定参数。

4. 基本使用示例(Lambda)


import java.util.concurrent.*;
public class ExecutorServiceDemo {
    public static void main(String[] args) throws Exception {
        // 创建固定大小线程池
        ExecutorService executor = Executors.newFixedThreadPool(3);
        // 提交 Runnable 任务
        executor.execute(() -> System.out.println("Runnable 任务执行"));
        // 提交 Callable 任务并获取结果
        Future<String> future = executor.submit(() -> {
            Thread.sleep(1000);
            return "Callable 返回结果";
        });
        System.out.println(future.get()); // 阻塞直到得到结果
        // 提交批量任务
        executor.invokeAll(List.of(
            () -> { System.out.println("任务1"); return 1; },
            () -> { System.out.println("任务2"); return 2; }
        ));
        // 关闭线程池(通常放在 finally 或 try-with-resources 中)
        executor.shutdown();
        // 等待所有任务完成
        if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
            executor.shutdownNow();
        }
    }
}

5. ExecutorService vs 直接创建 Thread

维度直接 new ThreadExecutorService
线程创建开销每次 new 都创建新线程,开销大复用线程,开销小
资源控制无限制,易导致系统过载可限制最大线程数,有任务队列
任务管理无法统一管理可监控、取消、等待、批量提交
关闭需要手动追踪每个线程一键 shutdown,优雅关闭
返回值仅能通过共享变量或回调支持 Future 获取结果和异常
定时/延迟需要自己写循环或 Timer直接支持 schedule

6. 注意事项与最佳实践

  • 务必关闭线程池:使用 shutdown() 或 shutdownNow(),避免资源泄漏。可使用 try-with-resources(Java 19+ 虚拟线程?普通线程池需手动关闭)。
  • 不要用无界队列FixedThreadPool 使用无界 LinkedBlockingQueue,若任务提交速度远超处理速度,队列会无限膨胀导致 OOM。推荐直接构造 ThreadPoolExecutor 并指定有界队列和拒绝策略。
  • 异常处理submit(Runnable) 如果任务抛异常,不会打印堆栈,除非通过 Future.get() 获取;execute(Runnable) 会直接抛出异常(但线程池会捕获并记录)。建议统一处理 ThreadFactory 中的未捕获异常处理器。
  • 虚拟线程:Java 21+ 引入了虚拟线程(Executors.newVirtualThreadPerTaskExecutor()),对于高并发 I/O 密集型任务,虚拟线程比传统线程池更轻量。但仍需理解 ExecutorService 的概念。

7. 示例:自定义 ThreadPoolExecutor


int corePoolSize = 5;
int maxPoolSize = 10;
long keepAliveTime = 60L;
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(100);
ThreadFactory threadFactory = Executors.defaultThreadFactory();
RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.SECONDS,
    workQueue, threadFactory, handler
);

总结

ExecutorService 是 Java 多线程编程中管理线程的标准方式。它提供了线程复用、任务队列、生命周期控制等关键能力,远比手动创建 Thread 更安全、高效。在实际项目中,除非极简单的场景,否则都应该使用 ExecutorService(或直接使用 ThreadPoolExecutor)来管理线程。对于 Java 21 以上版本,可以结合虚拟线程获得更好的 I/O 密集型任务性能。

Built with LogoFlowershow