Java之线程池
安全首选: 通常优先使用 shutdown() + awaitTermination() 组合实现优雅关闭。强制手段: 当需要快速关闭或优雅关闭超时时,使用 shutdownNow()。任务中断: shutdownNow() 的有效性依赖于你的任务代码能正确响应中断(即周期性地检查中断状态或在可能阻塞的操作中捕获 InterruptedException)。资源释放: 无
Java之线程池
Java线程池是Java并发编程中管理多线程的核心机制,它解决了频繁创建和销毁线程带来的性能开销问题,提供了一种限制和管理资源(包括线程)的高效方式。主要由java.util.concurrent包中的ExecutorService接口及其实现类(尤其是ThreadPoolExecutor)支持。
核心组成部分和工作原理
:
1.核心概念:
- 任务 (Runnable / Callable): 需要在线程中执行的代码单元。Runnable不返回结果,Callable可以返回结果。
- 工作线程 (Worker Threads): 线程池中实际执行任务的线程。
- 任务队列 (BlockingQueue): 用于存放尚未被执行的任务。当所有工作线程都忙时,新提交的任务会进入此队列等待。
- 线程池对象 (ThreadPoolExecutor / ScheduledThreadPoolExecutor): 负责创建、管理线程,接收提交的任务,分配任务给线程执行,并处理拒绝策略。
2.线程池的核心参数 (ThreadPoolExecutor构造函数): - corePoolSize (核心线程数): 线程池长期保持的线程数量。即使这些线程空闲,也不会被销毁(除非设置allowCoreThreadTimeOut(true))。
- maximumPoolSize (最大线程数): 线程池允许创建的最大线程数量。
- keepAliveTime (空闲线程存活时间): 当线程数超过corePoolSize时,空闲线程在多长时间后会被终止销毁。
- unit (时间单位): keepAliveTime的时间单位(毫秒、秒、分钟等)。
- workQueue (任务队列): 存放待执行任务的阻塞队列。常用队列:
- ArrayBlockingQueue: 有界队列,基于数组。
- LinkedBlockingQueue: 通常用作无界队列(默认Integer.MAX_VALUE,但有内存溢出风险),也可指定界限。Executors.newFixedThreadPool使用此队列。
- SynchronousQueue: 不存储元素的队列。每个插入操作必须等待一个对应的移除操作。Executors.newCachedThreadPool使用此队列。
- PriorityBlockingQueue: 带优先级的无界队列。
- threadFactory (线程工厂): 用于创建新线程的工厂。可以自定义线程名、优先级、守护状态等。
- handler (拒绝策略): 当任务队列已满且线程数已达maximumPoolSize时,处理新提交的任务的策略。JDK内置策略:
- AbortPolicy (默认): 直接抛出RejectedExecutionException异常。
- CallerRunsPolicy: 由提交任务的线程(调用execute方法的线程)自己执行该任务(比如主线程)。
- DiscardPolicy: 直接丢弃新提交的任务,没有任何通知。
- DiscardOldestPolicy: 丢弃任务队列中最旧的一个任务,然后尝试提交新任务。
3.任务提交与执行流程 (execute()方法):
a.提交任务。
b.如果当前运行的线程数 少于 corePoolSize,则立即创建新线程来执行任务。
c.如果运行的线程数等于或超过 corePoolSize,则尝试将任务放入任务队列。
d.如果任务队列已满: - 检查当前运行的线程数是否小于 maximumPoolSize。如果小于,则创建新线程立即执行这个新任务。
- 如果当前运行的线程数已达到或等于 maximumPoolSize,则触发拒绝策略。
e.当一个线程完成任务时,它会从队列中取出下一个任务来执行。
f.如果某线程空闲时间超过keepAliveTime,并且当前线程数大于 corePoolSize,则终止该线程。
Java提供的常用线程池(通过Executors工厂类创建):
1.FixedThreadPool: - Executors.newFixedThreadPool(int nThreads)
- 核心线程数 = 最大线程数 = nThreads。
- 使用一个无界队列 (LinkedBlockingQueue)。
- 适用场景:任务量已知,需要控制并发线程数量,负载较重的服务器。
- 风险:无界队列可能导致内存溢出(OOM)(任务无限堆积)。
2.CachedThreadPool: - Executors.newCachedThreadPool()
- 核心线程数 = 0。
- 最大线程数 = Integer.MAX_VALUE(理论上无限创建线程)。
- 使用SynchronousQueue。
- 空闲线程存活时间 = 60秒。
- 适用场景:大量短期异步任务,如处理大量HTTP请求。
- 风险:线程数量失控(任务过多时大量创建线程消耗资源,可能导致OOM)。对线程数量需要有上限的场景不适用。
3.SingleThreadExecutor: - Executors.newSingleThreadExecutor()
- 核心线程数 = 最大线程数 = 1。
- 使用一个无界队列 (LinkedBlockingQueue)。
- 适用场景:保证所有任务按提交顺序在一个线程中执行,不需要并行。
- 风险:同FixedThreadPool,无界队列可能导致OOM。
4.ScheduledThreadPool / SingleThreadScheduledExecutor: - Executors.newScheduledThreadPool(int corePoolSize)
- Executors.newSingleThreadScheduledExecutor()
- 基于ScheduledThreadPoolExecutor。
- 适用场景:执行定时任务或周期性任务。
- 使用延迟队列(通常是DelayedWorkQueue)。
- SingleThreadScheduledExecutor保证顺序执行。
重要方法和最佳实践
:
1.方法:
- void execute(Runnable command): 提交一个不需要返回值的任务。
- Future submit(Callable task): 提交一个有返回值的任务。
- Future submit(Runnable task, T result): 提交一个Runnable任务并指定一个结果(通常用于占位)。
- void shutdown(): 平滑关闭线程池。停止接收新任务,等待已提交任务执行完成(包括队列中的任务)。
- List shutdownNow(): 尝试立即停止所有正在执行的任务(通过Thread.interrupt(),但任务不保证能停止),暂停处理队列任务,返回等待执行的任务列表。
- boolean isShutdown(): 检查是否已调用shutdown或shutdownNow。
- boolean isTerminated(): 检查所有任务是否都已终止(执行完成)。
- boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException: 等待一段时间,让线程池关闭并完成剩余任务。
2.最佳实践: - 手动创建ThreadPoolExecutor:强烈推荐使用new ThreadPoolExecutor(…)手动配置参数,而不是依赖于Executors工厂方法(如newFixedThreadPool, newCachedThreadPool)。这样可以更清晰地了解队列大小限制等关键配置,避免OOM风险。
- 合理配置参数:
- CPU密集型任务:线程数 ≈ CPU核心数 (Runtime.getRuntime().availableProcessors()),避免过多线程上下文切换。通常设置为 N_cpu + 1。
- IO密集型任务(如大量数据库、网络请求):由于线程会阻塞在IO上,线程数可以设置得更大,如 2 * N_cpu,或者根据具体的IO等待时间调整。目标是尽量压榨CPU,避免CPU空闲。
- 使用有界队列并设置合理的拒绝策略:防止任务无限堆积导致OOM。选择合适的拒绝策略,根据业务场景决定是记录日志、丢弃部分任务还是回退。
- 命名线程 (使用自定义ThreadFactory):设置易于识别的线程名称(如myapp-pool-1-thread-1),方便调试和监控。
- 不要忘记关闭线程池:应用程序退出时,通过shutdown或shutdownNow关闭线程池。
- 避免在任务中抛出未捕获异常:未捕获异常会导致执行该任务的线程终止。可以在Runnable.run()方法内部使用try-catch,或者通过Thread.UncaughtExceptionHandler处理。
总结:
Java线程池是管理并发任务执行的强大工具。理解其核心参数(corePoolSize, maximumPoolSize, workQueue, handler)和工作流程是高效、安全使用线程池的关键。强烈建议手动创建ThreadPoolExecutor实例,根据具体任务性质(CPU密集型/IO密集型)、系统资源和可容忍的队列延迟需求,仔细配置这些参数,并为队列设置合理的界限和拒绝策略,以防止资源耗尽。Executors工厂类创建的预定义线程池虽方便,但存在显著缺陷(主要是无界队列或无限线程数),在生产环境中应谨慎使用或避免使用。
核心关闭方法
1.**shutdown()** (安全关闭)
- 功能:
- 停止接受新的任务。
- 继续执行已经提交的任务(包括正在运行的任务和队列中等待的任务),直到所有已提交任务完成。
- 特点:
- 优雅、安全地关闭线程池。
- 调用后,尝试提交新任务会触发 RejectedExecutionException(取决于配置的拒绝策略,默认为 AbortPolicy)。
- 不会中断正在执行的任务。任务会运行到正常结束。
- 使用场景:当你希望确保所有已提交任务都完成后再关闭线程池时使用。
2.**shutdownNow()** (立即关闭) - 功能:
- 尝试停止所有正在执行的任务(通过向工作线程调用 Thread.interrupt() 方法)。
- 停止处理队列中等待的任务。
- 返回 一个 List,其中包含队列中被丢弃的、尚未执行的任务。
- 特点:
- 更激进地尝试立即停止线程池。
- 调用后,尝试提交新任务会触发 RejectedExecutionException。
- 核心在于 interrupt(): 它只是向工作线程发送中断信号。任务是否真的能被停止,取决于任务本身对中断信号 (InterruptedException / Thread.interrupted()) 的响应性。如果任务是纯计算型或忽略了中断,它可能不会被中断。
- 使用场景:当你需要尽快关闭线程池,即使有些任务未完成也没关系,或者需要获取未执行的任务列表时使用(如需要将这些任务保存下来后续处理)。
配合使用的辅助方法
为了更有效地控制关闭过程,常与上述方法配合使用:
1.**isShutdown()**
- 功能:返回 boolean,表示是否已经调用了 shutdown() 或 shutdownNow() 方法。调用后立即返回 true,无论线程池中的任务是否已经执行完毕。
- 用途:检查线程池是否已经开始关闭过程。
2.**isTerminated()** - 功能:返回 boolean,表示所有任务是否都已执行完毕且所有工作线程都已关闭(即线程池已经完全终止)。仅在调用 shutdown() 或 shutdownNow() 之后才可能返回 true。
- 用途:检查线程池是否已经完全停止,没有剩余任务或线程。
3.**awaitTermination(long timeout, TimeUnit unit)** (常用!) - 功能:
- 阻塞调用此方法的线程。
- 阻塞直到发生以下情况之一:
- 所有任务执行完毕且所有线程关闭(即线程池 terminated) -> 返回 true。
- 超过指定的 timeout 时间 -> 返回 false。
- 当前线程在等待期间被中断 -> 抛出 InterruptedException。
- 参数:
- timeout: 等待的最大时间。
- unit: timeout 的时间单位(如 TimeUnit.SECONDS)。
- 用途:在使用 shutdown() 或 shutdownNow() 之后,调用此方法可以主动等待线程池完全关闭一段时间。如果返回 false 表示超时,你可以决定是否继续等待或执行其他操作(如强制退出、记录警告等)。这是实现优雅关闭的关键方法。
最佳实践:关闭线程池的标准流程
java运行复制ExecutorService executor = …; // 获取你的 ThreadPoolExecutor 实例
// 1. 停止接受新任务
executor.shutdown(); // 或者 executor.shutdownNow(); 取决于需求
try {
// 2. 等待一段时间让已提交任务完成 (这里使用 awaitTermination)
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
// 3. 如果超时还没终止完,可以选择强制关闭(shutdownNow)或记录警告/清理
executor.shutdownNow(); // 尝试立即停止剩余任务
// 4. (可选)再等待一小段时间强制停止后收尾
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
System.err.println(“线程池未能完全终止!”);
}
}
} catch (InterruptedException ie) {
// 5. 处理中断:立即强制停止
executor.shutdownNow();
// 6. 恢复中断状态(保持中断信息)
Thread.currentThread().interrupt();
}
关键要点总结
- 安全首选: 通常优先使用 shutdown() + awaitTermination() 组合实现优雅关闭。
- 强制手段: 当需要快速关闭或优雅关闭超时时,使用 shutdownNow()。
- 任务中断: shutdownNow() 的有效性依赖于你的任务代码能正确响应中断(即周期性地检查中断状态或在可能阻塞的操作中捕获 InterruptedException)。
- 资源释放: 无论使用哪种关闭方法,关闭后都应避免再次使用该线程池实例(提交新任务将触发异常)。成功关闭后,线程池会释放资源。
更多推荐
所有评论(0)