java教程笔记(十四)-线程池
Java 的线程池(ThreadPool
)是并发编程中用于管理和复用线程的重要机制。它通过预先创建一组线程并重复使用它们来执行多个任务,从而减少频繁创建和销毁线程的开销,提高程序性能。
1.线程池的核心概念
1. 线程池的作用
- 降低资源消耗:避免频繁创建/销毁线程。
- 提高响应速度:任务到达后可以直接使用已有线程执行。
- 提高线程可管理性:统一管理线程生命周期、限制最大并发数、防止资源耗尽。
2.Java 中线程池的实现类:ThreadPoolExecutor
ThreadPoolExecutor
是 Java 标准库中最核心的线程池实现类,构造函数如下:
推荐通过构造函数配置参数自定义创建线程池
ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数说明:
参数名 | 描述 |
---|---|
corePoolSize | 核心线程数(常驻线程数) |
maximumPoolSize | 最大线程数 |
keepAliveTime | 非核心线程空闲超时时间 |
unit | 超时单位 |
workQueue | 任务队列 |
threadFactory | 线程工厂,用于创建新线程 |
handler | 拒绝策略 |
3.线程池的工作流程
-
提交任务时:
- 如果当前线程数 <
corePoolSize
,创建新线程处理任务。 - 如果当前线程数 >=
corePoolSize
,将任务放入队列。 - 如果队列已满且当前线程数 <
maximumPoolSize
,创建新线程处理任务。 - 如果队列已满且线程数 >=
maximumPoolSize
,执行拒绝策略。
- 如果当前线程数 <
-
空闲线程会根据
keepAliveTime
判断是否回收(非核心线程优先回收)。
4.常见的线程池类型(来自 Executors
工具类)
Java 提供了几个常用使用 Executors
工具类创建线程池的方法:
线程池类型 | 特点 | 使用场景 |
---|---|---|
newFixedThreadPool | 核心线程数 = 最大线程数,所有线程都是核心线程 线程不会超时回收,会一直存活 使用无界队列 | 适用于负载较重、任务量稳定的系统 |
newCachedThreadPool | 核心线程数为 0,最大线程数为 空闲线程超时时间为 60 秒,超过时间会被回收 使用同步队列 | 适用于执行大量短期异步任务 |
newSingleThreadExecutor | 只有一个线程工作的线程池 所有任务按顺序执行(保证串行) 使用无界队列 | 保证任务串行执行,避免并发问题 |
newScheduledThreadPool | 支持定时和周期性任务调度 使用延迟队列 可以提交 | 定时调度、延迟执行等场景 |
newWorkStealingPool (JDK8+) | 使用 ForkJoinPool 实现,支持工作窃取算法 | 大规模并行任务调度 |
示例代码:
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.submit(() -> System.out.println("Task executed"));
5.拒绝策略(RejectedExecutionHandler)
当线程池无法处理新任务时,会调用拒绝策略。常见的内置策略有:
策略 | 行为 |
---|---|
AbortPolicy (默认) | 抛出 RejectedExecutionException 异常 |
CallerRunsPolicy | 由提交任务的线程自己执行任务 |
DiscardPolicy | 直接丢弃任务,不抛异常 |
DiscardOldestPolicy | 丢弃队列中最老的任务,尝试重新提交当前任务 |
自定义拒绝策略:
RejectedExecutionHandler handler = (r, executor) -> { System.out.println("Task rejected: " + r.toString()); };
6.创建线程池之后,如何使用
线程池创建之后,主要通过提交任务来使用它。Java 中的线程池(ExecutorService
)提供了多种方式来提交任务,并支持同步、异步、定时等执行模式。
1.提交任务的方式
1. execute(Runnable command)
- 用途:提交一个不需要返回结果的任务。
- 特点:
- 异步执行
- 不返回结果
- 不抛出异常(需手动捕获)
ExecutorService executor = Executors.newFixedThreadPool(4);executor.execute(() -> { System.out.println("Task executed in thread: " + Thread.currentThread().getName()); });
2. submit(Runnable task)
- 用途:提交一个没有返回值的任务,返回一个
Future
对象。 - 特点:
- 可用于跟踪任务是否完成
- 调用
future.get()
可以阻塞等待任务结束
Future<?> future = executor.submit(() -> { System.out.println("Running task..."); }); future.get(); // 阻塞直到任务完成
3. submit(Callable<T> task)
- 用途:提交一个有返回值的任务。
- 特点:
- 返回泛型结果
- 支持异常抛出
- 使用
Future.get()
获取结果或捕获异常
Future<Integer> future = executor.submit(() -> { return 42; });System.out.println("Result: " + future.get()); // 输出 42
4. invokeAll(Collection<? extends Callable<T>> tasks)
- 用途:批量提交多个
Callable
任务,等待所有任务完成。 - 特点:
- 返回
List<Future<T>>
- 可设置超时时间
- 返回
List<Callable<String>> tasks = Arrays.asList( () -> "Task 1", () -> "Task 2", () -> "Task 3" );
List<Future<String>> results = executor.invokeAll(tasks);
for (Future<String> result : results) { System.out.println(result.get()); }
5. invokeAny(Collection<? extends Callable<T>> tasks)
- 用途:提交多个任务,只要有一个任务完成就返回其结果,其他任务取消。
- 特点:
- 提升响应速度
- 常用于“最先完成者胜出”的场景
String result = executor.invokeAny(tasks); // 返回第一个完成的结果 System.out.println("First completed: " + result)
2.定时任务调度(适用于 ScheduledExecutorService
)
如果使用的是 newScheduledThreadPool
,可以使用以下方法:
1. schedule(Runnable command, long delay, TimeUnit unit)
- 用途:延迟执行一次任务
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
scheduler.schedule(() -> System.out.println("Delayed task"), 3, TimeUnit.SECONDS);
2. scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
- 用途:固定频率执行任务(即使上一次任务未完成,也会在周期到达时启动下一次)
scheduler.scheduleAtFixedRate(() -> { System.out.println("Fixed rate task"); }, 0, 1, TimeUnit.SECONDS);
3. scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
- 用途:固定延迟执行任务(每次任务完成后,再等待指定时间)
scheduler.scheduleWithFixedDelay(() -> { System.out.println("Fixed delay task"); }, 0, 1, TimeUnit.SECONDS);
3.关闭线程池
线程池使用完毕后必须关闭,否则可能导致程序无法正常退出。
1. shutdown()
- 作用:不再接受新任务,但会等待已提交任务执行完毕。
executor.shutdown();
2. shutdownNow()
- 作用:尝试立即停止所有正在执行的任务,并返回尚未执行的任务列表。
List<Runnable> pendingTasks = executor.shutdownNow();
3. 等待关闭完成(可选)
if (!executor.isShutdown()) {
executor.shutdown();
}
try { if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();}
}
catch (InterruptedException e) { executor.shutdownNow(); }
4.完整示例
import java.util.concurrent.*;public class ThreadPoolUsage {public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(4);
// 提交 Runnable 任务executor.execute(() -> System.out.println("Hello from execute"));
// 提交 Callable 任务并获取结果
Future<Integer> future = executor.submit(() -> { return 100; }); System.out.println("Callable result: " + future.get()); // 定时任务 ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2); scheduler.schedule(() -> System.out.println("Delayed task"), 2, TimeUnit.SECONDS);
// 关闭线程池
executor.shutdown();
scheduler.shutdown();}
}
7.总结
操作 | 方法 | 说明 |
---|---|---|
提交无返回任务 | execute(Runnable) | 最简单的异步执行方式 |
提交无返回任务 | submit(Runnable) | 返回 Future 可判断是否完成 |
提交有返回任务 | submit(Callable) | 支持返回值和异常处理 |
批量提交任务 | invokeAll() | 返回所有任务的 Future 列表 |
任一任务完成即返回 | invokeAny() | 适用于多任务中取最快结果 |
定时/周期任务 | schedule*() | 适用于 ScheduledExecutorService |
关闭线程池 | shutdown() / shutdownNow() | 必须显式调用 |
合理使用这些方法,可以充分发挥线程池的优势,提高并发性能与资源利用率。
8.线程池的监控与关闭
1. 常用监控方法:
方法 | 说明 |
---|---|
getPoolSize() | 获取当前线程池中的线程数量 |
getActiveCount() | 获取正在执行任务的线程数 |
getCompletedTaskCount() | 获取已完成的任务数 |
getQueue() | 获取等待执行的任务队列 |
boolean isShutdown() | 判断线程池是否已关闭 |
boolean isTerminated() | 判断线程池是否完全终止(所有任务执行完并关闭) |
boolean awaitTermination(long timeout, TimeUnit unit) | 等待线程池终止 |
9.最佳实践
- 合理设置核心线程数和最大线程数,避免资源浪费或线程爆炸。
- 选择合适的任务队列(如
LinkedBlockingQueue
,ArrayBlockingQueue
)。 - 避免任务堆积,设置合理的拒绝策略。
- 及时关闭线程池,防止内存泄漏。
- 使用
Future
或CompletableFuture
获取任务结果或异常信息。