Java 线程池是一个提供多线程管理和调度的工具,通常用来处理多个并发任务。线程池能够帮助有效管理线程的创建、调度、执行和销毁,避免频繁的线程创建和销毁,提高系统性能。
前言
Java 线程池是面试中的常客,面试官经常会问线程池的作用和线程池的创建销毁这些基础问题,本文就Java线程池的基本概念、工作原理、实际案例展开阐述。
一、基本概念
线程池本质上是一个管理线程的容器,它包含了多个线程,可以用来执行多个任务。线程池的核心思想是:复用已创建的线程,避免频繁的线程创建和销毁操作。
为什么要使用线程池:
- 性能稳定性强:通过复用线程,避免频繁创建和销毁线程带来的开销。
- 提高资源管理效率:线程池管理线程的最大数量,防止线程过多而导致资源竞争和系统崩溃。
- 配置灵活:线程池的参数可以根据实际需求调整,允许在不同的任务量和工作负载下调整线程池的大小。
- 支持定时以及周期性任务执行:线程池可以方便地支持定时任务和周期性任务的执行,这对于需要定时执行任务的应用非常有用。
二、核心接口
在Java中,线程池的核心接口和类主要位于java.util.concurrent包中。线程池结构图如下:
 
1. Executor 接口
Executor 是线程池的最基本接口,它负责提交任务。定义了执行任务的基本方法。
public interface Executor {
    void execute(Runnable command); // 提交任务给线程池
}
2. ExecutorService 接口
ExecutorService 是 Executor 的子接口,增加了用于管理线程池的方法。常用的方法包括:
- submit()提交任务并返回一个 Future 对象,允许获取任务执行结果或取消任务。
- invokeAll()提交多个任务并返回一个 List,用于批量处理任务。
- shutdown()和- shutdownNow()用于关闭线程池。
public interface ExecutorService extends Executor {
    <T> Future<T> submit(Callable<T> task); // 提交带返回值的任务
    <T> Future<T> submit(Runnable task, T result); // 提交Runnable任务,并返回结果
    List<Runnable> shutdownNow(); // 强制关闭线程池
    void shutdown(); // 优雅地关闭线程池
}
3. ThreadPoolExecutor 类
ThreadPoolExecutor 是 ExecutorService 的核心实现类,提供了灵活配置线程池参数的方法。常用的构造函数如下:
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue)
- corePoolSize:核心线程数,线程池最小线程数。
- maximumPoolSize:最大线程数,线程池最多允许的线程数。
- keepAliveTime:当线程池的线程超过 corePoolSize 时,空闲线程的最大存活时间。
- unit:keepAliveTime 的时间单位。
- workQueue:任务队列,用于存储等待执行的任务。
常用线程池类:
- newFixedThreadPool(int nThreads):创建固定大小的线程池,nThreads 为线程数。
- newCachedThreadPool():创建可缓存的线程池,能够根据需要创建线程,线程空闲时会被回收。
- newSingleThreadExecutor():创建单线程池,始终只有一个工作线程。
- newScheduledThreadPool(int corePoolSize):创建定时任务线程池,支持定时和周期性任务。
三、线程池创建关闭
1、使用 ExecutorService 创建和使用线程池
import java.util.concurrent.*;
public class ThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个线程池,最大线程数为 10,核心线程数为 5,任务队列长度为 100
        ExecutorService executorService = new ThreadPoolExecutor(
            5, 10, 60L, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(100)
        );
        // 提交 10 个任务
        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            executorService.submit(() -> {
                System.out.println("Executing task " + taskId + " by " + Thread.currentThread().getName());
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }
        // 关闭线程池
        executorService.shutdown();
    }
}
2、直接使用ThreadPoolExecutor类创建线程池
可以直接使用ThreadPoolExecutor类构造器来创建线程池,这样可以更灵活地设置参数,如核心线程数、最大线程数、工作队列、线程工厂、拒绝策略等。
int corePoolSize = 10;
int maximumPoolSize = 50;
long keepAliveTime = 120;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(100);
ThreadFactory threadFactory = Executors.defaultThreadFactory();
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
ExecutorService threadPool = new ThreadPoolExecutor(
    corePoolSize,
    maximumPoolSize,
    keepAliveTime,
    unit,
    workQueue,
    threadFactory,
    handler
);
3、关闭线程池
- shutdown():线程池会等到已经提交的任务执行完成后关闭,无法接受新的任务。
- shutdownNow():线程池尝试停止所有正在执行的任务,并返回待执行任务的列表。
四、线程池工作原理
当一个任务被提交到线程池时,线程池根据其当前的状态决定如何处理任务:
- 如果当前工作线程数小于 corePoolSize,线程池会创建一个新的线程来处理任务。
- 如果当前工作线程数等于或大于 corePoolSize,且队列尚未满,线程池将任务添加到队列中。
- 如果当前工作线程数大于 corePoolSize且队列已满,线程池会创建新的线程,直到达到maximumPoolSize。
- 如果所有线程都在工作且队列已满,新的任务将被拒绝,具体的拒绝策略取决于 RejectedExecutionHandler的实现(默认是抛出异常)。
线程池任务处理流程图如下:
 
五、线程池异常处理
1. ThreadPoolExecutor 的异常处理机制
 在使用 ThreadPoolExecutor 提交任务时,任务执行过程中的异常处理取决于几个因素,尤其是任务的类型(Runnable 或 Callable)以及异常的传播方式。
- Runnable接口中的异常
 Runnable 是不返回结果的任务接口,它的- run()方法不能抛出任何异常。因此,如果在- run()方法中发生了异常,默认情况下,异常会被吞掉,线程池不会进行任何处理。
 默认行为:
 线程池不会捕获- Runnable中的异常。异常会在执行该任务的线程中被丢弃,不会传播到线程池外部。任务失败的信息不会返回给调用者。如果想要捕获并处理- Runnable中的异常,你需要在- run()方法内部手动捕获异常,并进行相应的处理。
Runnable task = () -> {
    try {
        // 任务代码
        throw new RuntimeException("Error in task");
    } catch (Exception e) {
        System.out.println("Exception caught: " + e.getMessage());
        // 处理异常,如记录日志
    }
};
executor.execute(task);
- Callable接口中的异常
 Callable 是带有返回值的任务接口,它的- call()方法允许抛出异常。如果任务中的- call()方法抛出异常,线程池会将异常封装到- Future对象中,调用者可以通过- Future.get()方法获取异常。
 处理 Callable 中的异常:
 如果- call()方法抛出异常,- Future.get()会抛出 ExecutionException,并且原始异常会作为 ExecutionException 的- cause。
 可以通过- Future.get()捕获并处理异常。
Callable<String> task = () -> {
    if (true) {
        throw new RuntimeException("Error in callable task");
    }
    return "Task Completed";
};
ExecutorService executorService = Executors.newFixedThreadPool(1);
Future<String> future = executorService.submit(task);
try {
    // 获取结果,如果有异常会抛出 ExecutionException
    String result = future.get();
    System.out.println(result);
} catch (ExecutionException e) {
    System.out.println("Task failed with exception: " + e.getCause());
} catch (InterruptedException e) {
    Thread.currentThread().interrupt(); // 恢复中断状态
}
2. 线程池的异常处理机制:RejectedExecutionHandler
 当线程池的工作队列已满,或线程池的线程数已经达到最大值时,任务会被拒绝执行。这时,线程池使用 RejectedExecutionHandler 来处理拒绝的任务。
RejectedExecutionHandler 是一个接口,它有四个常用实现:
- AbortPolicy:默认策略,抛出 RejectedExecutionException。
- CallerRunsPolicy:由提交任务的线程来执行该任务。
- DiscardPolicy:丢弃被拒绝的任务。
- DiscardOldestPolicy:丢弃队列中最旧的任务,并执行当前任务。
如果线程池中的任务被拒绝执行,RejectedExecutionHandler 会被调用,允许我们自定义如何处理这些被拒绝的任务。可以在此处捕获和处理任务拒绝相关的异常。
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    1, 1, 0L, TimeUnit.MILLISECONDS, 
    new LinkedBlockingQueue<>(1),
    new ThreadPoolExecutor.DiscardOldestPolicy() // 自定义的拒绝策略
);
Runnable task = () -> {
    System.out.println("Executing task");
};
for (int i = 0; i < 5; i++) {
    executor.execute(task); // 会抛出被拒绝的任务
}
3. 自定义异常处理机制
 可以通过 ThreadFactory 来为线程池中的每个线程提供自定义的异常处理机制。在 ThreadFactory 创建线程时,可以设置线程的 uncaughtExceptionHandler,这样可以捕获线程执行时未处理的异常。
ThreadFactory threadFactory = new ThreadFactory() {
    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(r);
        thread.setUncaughtExceptionHandler((t, e) -> {
            System.out.println("Thread " + t.getName() + " failed with exception: " + e.getMessage());
        });
        return thread;
    }
};
ExecutorService executorService = new ThreadPoolExecutor(
    1, 1, 0L, TimeUnit.MILLISECONDS, 
    new LinkedBlockingQueue<>(1),
    threadFactory
);
executorService.submit(() -> {
    throw new RuntimeException("Exception in thread");
});
六、线程池应用场景
1、固定大小线程池
固定线程池(FixedThreadPool)是线程池的一种实现方式,它通过固定数量的线程来执行提交的任务。在这种线程池中,线程的数量是固定的,线程池的大小在创建时被设定好,且不会发生变化。即使有大量的任务提交,线程池也只会使用有限数量的线程去处理任务,超出线程池容量的任务将被放入等待队列,直到线程池中的某个线程完成任务并空闲出来,才能继续执行新的任务。适用于任务量比较稳定的场景,可以高效管理线程资源。示意图如下:

 使用 FixedThreadPool 创建线程池
import java.util.concurrent.*;
public class FixedThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个固定大小的线程池,线程数为 3
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        // 提交 5 个任务
        for (int i = 0; i < 5; i++) {
            final int taskId = i;
            executorService.submit(() -> {
                try {
                    System.out.println("Task " + taskId + " is being executed by " + Thread.currentThread().getName());
                    // 模拟任务执行
                    Thread.sleep(1000);
                    System.out.println("Task " + taskId + " completed by " + Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }
        // 关闭线程池
        executorService.shutdown();
    }
}
2、缓存型线程池
CachedThreadPool 是一种能够根据需求动态创建线程的线程池实现,其特点是当任务较少时,线程池的大小可以非常小,甚至为 0;当任务增多时,线程池可以根据需要动态创建新的线程来处理任务。线程池中的线程在任务完成后不会立即销毁,而是会被缓存一段时间。如果有新的任务提交,线程池会复用这些空闲的线程;如果任务长时间没有提交,空闲的线程会被销毁。适用于任务量不稳定的场景,可以根据任务需求动态增加线程数,线程空闲时会被回收。示意图如下:
 
使用 CachedThreadPool 创建线程池
ExecutorService executorService = Executors.newCachedThreadPool();
3、定时任务调度线程池
调度线程池可以设定一个周期,按照这个周期重复执行任务。适用于需要定时或周期性执行任务的场景。示意图如下:

 创建 ScheduledThreadPool
import java.util.concurrent.*;
public class ScheduledThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个大小为 3 的调度线程池
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
        // 1. 延迟 2 秒执行一次任务
        scheduledExecutorService.schedule(() -> {
            System.out.println("Task with delay executed at " + System.currentTimeMillis());
        }, 2, TimeUnit.SECONDS);
        // 2. 每 3 秒执行一次任务
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            System.out.println("Periodic task executed at " + System.currentTimeMillis());
        }, 1, 3, TimeUnit.SECONDS);
        // 3. 每 3 秒执行一次任务,首次任务会在延迟 1 秒后执行
        scheduledExecutorService.scheduleWithFixedDelay(() -> {
            System.out.println("Periodic task with fixed delay executed at " + System.currentTimeMillis());
        }, 1, 3, TimeUnit.SECONDS);
        // 4. 等待一段时间后关闭线程池
        try {
            Thread.sleep(10000); // 等待 10 秒钟,让任务有时间执行
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        scheduledExecutorService.shutdown();
    }
}
六、注意事项
- 线程池大小设置:根据系统的硬件资源(如CPU核心数)和任务的类型,适当调整线程池的大小。线程池过大会带来资源浪费,过小则可能导致任务执行延迟。
- 合理选择队列:使用适当的队列类型(如 LinkedBlockingQueue、ArrayBlockingQueue)可以优化线程池性能。
- 线程池监控与调试:在生产环境中,监控线程池的状态(如活跃线程数、等待任务数等)对于调优非常重要。
总结
Java 线程池是处理多线程并发任务的重要工具,能够有效管理线程的生命周期,提高性能和资源利用率。通过合理配置线程池参数和任务队列,可以根据业务需求优化线程池的性能。在实际开发中,需要根据任务的特点和系统的资源状况,选择合适的线程池类型和配置。



















