Created
Mar 28, 2024 01:27 AM
Tags
前言线程池的构造ThreadPoolExecutorRejectedExecutionHandlerBlockingQueue线程池使用线程池创建线程池状态提交任务异常处理任务异常处理线程池异常处理参考文档
前言
Java 中的线程池是一种线程管理机制,它可以在需要执行任务时重用线程,减少线程创建和销毁的开销,提高程序的性能。Java 提供了
java.util.concurrent
包来支持线程池的实现。线程池的构造
ThreadPoolExecutor
ThreadPoolExecutor
是 Java 中用于创建和管理线程池的主要类。您可以使用它来自定义线程池的行为,如核心线程数、最大线程数、线程存活时间、工作队列等。根据构造函数所传参数,线程池有以下行为:
- 任务数 < corePoolSize + workQueue.size,使用核心线程执行任务,其它任务在阻塞队列等待
- corePoolSize + workQueue.size < 任务数 < maxPoolSize + workQueue.size,(任务数 - workQueue.size)个线程执行任务,其他任务在阻塞队列等待
- 任务数 > maxPoolSize + workQueue.size,触发拒绝策略
RejectedExecutionHandler
当
任务数 > maxPoolSize + workQueue.size
时触发拒绝策略,以下是关于 RejectedExecutionHandler 接口常用实现类的展示:类名 | 描述 |
AbortPolicy | 默认的拒绝策略,直接抛出 RejectedExecutionException 异常。 |
CallerRunsPolicy | 在调用者线程中直接执行被拒绝的任务。 |
DiscardPolicy | 直接丢弃被拒绝的任务,不做任何处理。 |
DiscardOldestPolicy | 丢弃队列中等待时间最长的任务,然后尝试重新提交当前任务。 |
Custom 实现 | 您可以实现自定义的 RejectedExecutionHandler 接口来定义自己的拒绝策略。 |
这些拒绝策略可以根据您的需求来选择,以便更好地处理线程池中被拒绝的任务。
BlockingQueue
以下是关于常用的阻塞队列类型及其特性的展示:
类名 | 描述 |
ArrayBlockingQueue | 基于数组的有界阻塞队列,按照先进先出的顺序存储元素。需要指定容量大小。 |
LinkedBlockingQueue | 基于链表的有界或无界阻塞队列,按照先进先出的顺序存储元素。如果不指定容量,则为无界。 |
PriorityBlockingQueue | 无界阻塞队列,根据元素的自然顺序或者通过构造函数提供的 Comparator 进行优先级排序。 |
DelayQueue | 无界阻塞队列,用于存放实现了 Delayed 接口的元素,元素只有在延迟期满时才能被取出。 |
SynchronousQueue | 不存储元素的阻塞队列,生产者必须等待消费者取走元素,反之亦然。 |
LinkedTransferQueue | 无界阻塞队列,支持生产者等待消费者接收元素。 |
LinkedBlockingDeque | 双端阻塞队列,可以在两端插入和移除元素。 |
这些阻塞队列提供了不同的特性和适用场景,您可以根据需求选择最适合的阻塞队列类型。
线程池使用
线程池创建
- 通过 ThreadPoolExecutor 构造函数来创建(推荐的方式)
- 通过
Executor
框架的工具类Executors
来创建
以下是
Executors
类创建线程池的常用方法的展示:方法 | 描述 | 注意 |
newFixedThreadPool(int nThreads) | 创建固定大小的线程池,包含固定数量的线程。 | 使用的是无界的 LinkedBlockingQueue ,任务队列最大长度为 Integer.MAX_VALUE ,可能堆积大量的请求,从而导致 OOM。 |
newSingleThreadExecutor() | 创建单线程的线程池,保证任务按顺序执行。 | 使用的是无界的 LinkedBlockingQueue ,任务队列最大长度为 Integer.MAX_VALUE ,可能堆积大量的请求,从而导致 OOM。 |
newCachedThreadPool() | 创建可根据需要创建新线程的线程池。 | 使用的是同步队列 SynchronousQueue , 允许创建的线程数量为 Integer.MAX_VALUE ,如果任务数量过多且执行速度较慢,可能会创建大量的线程,从而导致 OOM。 |
newScheduledThreadPool(int corePoolSize) | 创建支持定时以及周期性任务执行的线程池。 | 使用的无界的延迟阻塞队列 DelayedWorkQueue ,任务队列最大长度为 Integer.MAX_VALUE ,可能堆积大量的请求,从而导致 OOM。 |
newWorkStealingPool() | 创建使用工作窃取算法的线程池。 | ㅤ |
线程池状态
在Java中,线程池有几种状态,主要包括以下几种:
- RUNNING(运行状态):线程池处于运行状态,可以接受新任务,并处理阻塞队列中的任务。
- SHUTDOWN(关闭状态):不再接受新任务,但会继续处理阻塞队列中的任务,直到队列为空。
- STOP(停止状态):不再接受新任务,不处理阻塞队列中的任务,会尝试中断正在执行的任务。
- TERMINATED(终止状态):线程池完全终止,不再处理任何任务。
这些状态可以通过线程池的方法来进行转换,
shutdown()
方法将线程池状态从 RUNNING 转换为 SHUTDOWN。
线程池的状态则立刻变成SHUTDOWN状态。此时,则不能再往线程池中添加任何任务,否则将会抛出RejectedExecutionException异常。但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。
shutdownNow()
方法将线程池状态从 RUNNING 转换为 STOP。
线程池的状态立刻变成 STOP 状态,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,当然,它会返回那些未执行的任务。
它试图终止线程的方法是通过调用 Thread.interrupt() 方法来实现的,这种方法的作用有限,如果线程中没有 sleep 、wait、Condition、定时锁等应用, interrupt() 方法是无法中断当前线程的。所以,shutdownNow() 并不代表线程池立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。
awaitTermination()
方法用于等待线程池进入 TERMINATED 状态。
awaitTermination 阻塞,直到所有任务在关闭请求后完成执行,或者发生超时,或者当前线程被中断,以先发生者为准。如果线程池已经关闭,则直接返回 true;如果线程池未关闭,该方法会根据 Timeout + TimeUnit 的延时等待线程结束,并根据到期后的线程池状态返回 true 或者 false,该方法不会关闭线程池,只负责延时以及检测状态。
提交任务
任务提交可以通过 ThreadPoolExecutor 的 execute() 方法或 submit() 方法来实现。
- execute()
execute() 方法用于提交不需要返回结果的任务,参数为 Runnable 对象。
- submit()
submit() 方法用于提交需要返回结果的任务,参数可以是 Runnable 或 Callable 对象,返回一个 Future 对象,通过 Future 对象可以获取任务的执行结果或取消任务。
在提交任务后,线程池会根据配置的核心线程数、最大线程数等参数来执行任务。如果线程池中的线程数小于核心线程数,会创建新线程来执行任务;如果线程池中的线程数达到核心线程数,会将任务放入工作队列中等待执行;如果工作队列已满,会根据最大线程数来创建新线程执行任务;如果线程数达到最大线程数且工作队列已满,会根据拒绝策略来处理无法执行的任务。
异常处理
在 Java 中,线程池的异常处理通常涉及到两个方面:一是如何处理线程池中任务的异常,二是如何处理线程池本身可能出现的异常。下面分别介绍这两个方面的处理方法:
任务异常处理
默认情况下,线程池内所有任务执行都被
try catch
包裹。- 使用Future对象获取任务执行结果并处理异常
通过 Future 对象的 get() 方法可以获取任务执行的结果,同时可以捕获任务执行过程中抛出的异常:
- 使用 UncaughtExceptionHandler 处理线程池中线程的未捕获异常
通过设置线程池的 UncaughtExceptionHandler,可以捕获线程池中线程抛出的未捕获异常:
线程池异常处理
- 使用 ThreadPoolExecutor 的 afterExecute() 方法处理任务执行过程中的异常
可以通过重写 ThreadPoolExecutor 的 afterExecute() 方法,在任务执行完成后处理任务执行过程中的异常:
- 使用RejectedExecutionHandler处理无法执行的任务
当线程池无法执行任务时,可以通过设置RejectedExecutionHandler来处理无法执行的任务:
通过以上方法,可以有效地处理线程池中任务的异常和线程池本身可能出现的异常,保证线程池的稳定运行。
参考文档
缺少异常处理的示例