Skip to content

线程池

概述

线程池(Thread Pool) 是一种复用线程的并发编程方式:预先创建若干线程,由它们反复执行提交的任务(Task),而不是为每个任务都「 new Thread().start()」。使用线程池可以降低线程创建与销毁的开销控制并发数量统一管理任务队列与拒绝策略,是生产环境中处理多任务的推荐做法。Java 通过 Executor 框架java.util.concurrent)提供线程池支持,核心接口是 ExecutorService,常用实现是 ThreadPoolExecutor。学习前建议已掌握 线程创建与生命周期同步与锁


为什么使用线程池

若每次需要并发执行任务时都 new Thread(task).start(),会带来以下问题:

  • 创建与销毁开销大:线程的创建和销毁涉及操作系统资源,频繁创建会浪费 CPU 与内存。
  • 线程数量难以控制:高并发时可能创建大量线程,导致上下文切换频繁甚至 OOM。
  • 任务与线程耦合:缺少统一的任务队列、取消、超时等能力。

线程池通过固定或可伸缩的一组工作线程 + 任务队列,实现任务与线程解耦、复用线程、限制并发度,从而更稳定、可控。


核心接口与类

类型说明
Executor顶层接口,仅定义 void execute(Runnable command)
ExecutorService继承 Executor,增加 submitshutdowninvokeAll 等生命周期与任务提交方法
ThreadPoolExecutor可配置的线程池实现类,通过构造方法指定核心线程数、最大线程数、队列、拒绝策略等
Executors工具类,提供 newFixedThreadPoolnewCachedThreadPool 等工厂方法(便捷但部分存在隐患)

日常开发中,通常通过 Executors 快速获得一个 ExecutorService,或直接使用 ThreadPoolExecutor 的构造方法进行精细配置。


基本用法

使用 Executors 创建线程池

Executors 提供几种常用工厂方法,适合快速上手和简单场景。

java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorsDemo {
    public static void main(String[] args) {
        // 固定大小的线程池:始终维持固定数量的工作线程
        ExecutorService pool = Executors.newFixedThreadPool(2);

        // 提交任务:execute 无返回值,适合 Runnable
        pool.execute(() -> System.out.println("任务1: " + Thread.currentThread().getName()));
        pool.execute(() -> System.out.println("任务2: " + Thread.currentThread().getName()));
        pool.execute(() -> System.out.println("任务3: " + Thread.currentThread().getName())); // 会排队等待

        // 使用完毕后关闭线程池(不再接受新任务,已提交任务会执行完)
        pool.shutdown();
    }
}

提交任务:execute 与 submit

  • execute(Runnable):只提交任务,不关心返回值或异常结果;任务抛出未捕获异常可能导致该工作线程退出(取决于默认的未捕获异常处理)。
  • submit(Runnable)submit(Callable<T>):返回 Future,可用来获取结果、取消任务或判断是否完成;任务中的异常会被包装在 Future.get() 抛出的 ExecutionException 中。
java
import java.util.concurrent.*;

public class SubmitDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(2);

        // submit(Callable) 可获取返回值
        Future<Integer> future = pool.submit(() -> {
            Thread.sleep(100);
            return 42;
        });

        System.out.println("计算结果: " + future.get()); // 阻塞直到得到 42

        // submit(Runnable) 返回 Future<?>,get() 得到 null
        Future<?> f2 = pool.submit(() -> System.out.println("Runnable 任务"));
        f2.get(); // 等待执行完成

        pool.shutdown();
    }
}

关闭线程池:shutdown 与 shutdownNow

  • shutdown():温和关闭。不再接受新任务,已提交的任务会继续执行完毕;调用后可根据 awaitTermination 等待一段时间或直到结束。
  • shutdownNow():立即尝试停止。会中断正在执行的任务、返回尚未执行的任务列表;正在执行的任务是否真正中断取决于任务是否响应中断。

提示

生产代码中建议先 shutdown(),再使用 awaitTermination(timeout, unit) 等待一段时间,若仍未结束再视情况调用 shutdownNow(),并妥善处理返回的未执行任务列表。

java
ExecutorService pool = Executors.newFixedThreadPool(2);
// ... 提交任务 ...

pool.shutdown();
try {
    if (!pool.awaitTermination(5, TimeUnit.SECONDS)) {
        pool.shutdownNow();
        if (!pool.awaitTermination(2, TimeUnit.SECONDS)) {
            System.err.println("线程池未能完全终止");
        }
    }
} catch (InterruptedException e) {
    pool.shutdownNow();
    Thread.currentThread().interrupt();
}

线程池核心参数(ThreadPoolExecutor)

要更精细地控制线程池行为,应直接使用 ThreadPoolExecutor 的构造方法。其核心参数包括:

参数含义
corePoolSize核心线程数:池中常驻的线程数量,即使空闲也可不回收(取决于 allowCoreThreadTimeOut)
maximumPoolSize最大线程数:池中允许存在的线程数量上限
keepAliveTime非核心线程的空闲存活时间,超时后回收
workQueue任务队列:核心线程都在忙时,新任务先进入该队列;队列满时才会在不超过 maximumPoolSize 的前提下创建非核心线程
threadFactory创建新线程的工厂,可用来命名线程、设为守护线程等,便于排查问题
handler拒绝策略:当队列已满且线程数达到 maximumPoolSize 时,新任务如何被拒绝(抛异常、调用者运行、丢弃等)

任务提交流程简述

  1. 若当前线程数 < corePoolSize,则创建新的核心线程执行该任务。
  2. 否则,将任务放入 workQueue。
  3. 若队列已满且当前线程数 < maximumPoolSize,则创建非核心线程执行该任务。
  4. 若队列已满且线程数已达 maximumPoolSize,则执行 RejectedExecutionHandler(拒绝策略)。

使用 ThreadPoolExecutor 构造线程池

java
import java.util.concurrent.*;

public class ThreadPoolExecutorDemo {
    public static void main(String[] args) {
        int corePoolSize = 2;
        int maxPoolSize = 4;
        long keepAlive = 60L;
        BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(100);
        RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();

        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                corePoolSize,
                maxPoolSize,
                keepAlive,
                TimeUnit.SECONDS,
                queue,
                Executors.defaultThreadFactory(),
                handler
        );

        executor.execute(() -> System.out.println("任务执行: " + Thread.currentThread().getName()));
        executor.shutdown();
    }
}

拒绝策略

当线程池「无法再接受新任务」时(队列满且线程数已达上限),会调用 RejectedExecutionHandler。常用策略如下:

策略说明
AbortPolicy(默认)抛出 RejectedExecutionException
CallerRunsPolicy由调用 execute/submit 的线程直接执行该任务,相当于限流回退到调用方
DiscardPolicy静默丢弃任务
DiscardOldestPolicy丢弃队列中最老的一个任务,然后再次尝试提交当前任务

可根据业务选择:需要感知「提交失败」时用 AbortPolicy;希望尽量不丢任务、能接受调用方变慢时可用 CallerRunsPolicy。


注意事项

注意

Executors.newFixedThreadPool(int)Executors.newSingleThreadExecutor() 内部使用无界LinkedBlockingQueue。若任务提交速度远大于处理速度,队列会无限增长,可能导致 OOM。生产环境更推荐使用 ThreadPoolExecutor 指定有界队列(如 ArrayBlockingQueue)并设置合理的拒绝策略。

注意

Executors.newCachedThreadPool() 的最大线程数为 Integer.MAX_VALUE,且没有任务队列缓冲。高并发下可能创建大量线程,导致上下文切换剧烈甚至 OOM。仅适合大量短生命周期、低并发的任务,使用时要谨慎评估。

提示

为便于排查问题,建议使用自定义 ThreadFactory 为线程命名(例如 pool-1-task-1),或使用 Guava 的 ThreadFactoryBuilder


相关链接

基于 VitePress 构建