线程池是一种用于管理和复用线程的机制,它可以在多线程应用程序中有效地处理并发任务。Java中的线程池是通过java.util.concurrent
包提供的Executor
框架实现的。
Executor 框架
Executor
框架结构主要由三大部分组成:
1.任务
任务是需要在Executor框架中执行的工作单元,可以是实现了Runnable
接口或Callable
接口的对象。Runnable
接口表示一个没有返回值的任务,而Callable
接口表示一个有返回值的任务。任务可以封装需要执行的代码逻辑,并可以被提交给执行器进行执行。任务可以是独立的、并行的,或者依赖其他任务的结果。
2.执行器
执行器是Executor框架的核心部分,负责管理和执行任务。它定义了任务的执行策略,包括任务的提交、调度和执行。执行器隐藏了底层线程的创建和管理细节,提供了更高级别的抽象。Java中的Executor
接口和ExecutorService
接口都是执行器的表示,它们提供了提交任务、执行任务和管理线程池的方法。
Executor
接口定义了最基本的任务执行方法execute(Runnable command)
,用于提交一个Runnable
任务给执行器。执行器可以根据自己的策略来决定如何执行任务,例如在当前线程执行任务、创建新线程执行任务等。ExecutorService
接口继承自Executor
接口,提供了更丰富的任务执行方法。除了execute()
方法外,还提供了支持任务结果返回的submit()
方法,以及关闭线程池的方法,如shutdown()
和shutdownNow()
。
Java提供了多个内置的执行器实现,如ThreadPoolExecutor
和ScheduledThreadPoolExecutor
,它们提供了可配置的线程池和任务调度功能。
(该图片来自JavaGuide )
3. 异步计算的结果
Future
接口以及 Future
接口的实现类 FutureTask
类都可以代表异步计算的结果。
当我们把 Runnable
接口 或 Callable
接口 的实现类提交给 ThreadPoolExecutor
或 ScheduledThreadPoolExecutor
执行。(调用 submit()
方法时会返回一个 FutureTask
对象)
(来自 JavaGuide)
ThreadPoolExecutor类
核心参数
corePoolSize
:核心线程池大小,表示线程池中保持活动状态的线程数量。即使线程处于空闲状态,核心线程也不会被销毁。maximumPoolSize
:最大线程池大小,表示线程池中允许存在的最大线程数量。当任务队列已满且活动线程数达到最大线程数时,新的任务将会触发创建额外的线程。keepAliveTime
:线程空闲时间,表示线程在没有任务可执行时保持存活的时间。超过该时间,多余的线程将被销毁,直到线程池大小不大于核心线程池大小。unit
:时间单位,用于指定keepAliveTime
的时间单位。workQueue
:任务队列,用于存放等待执行的任务。可以选择不同类型的队列,如ArrayBlockingQueue
、LinkedBlockingQueue
、SynchronousQueue
等。threadFactory
:线程工厂,用于创建新线程。handler
:拒绝策略,用于处理无法执行的任务。可以选择使用预定义的拒绝策略,如ThreadPoolExecutor.AbortPolicy
、ThreadPoolExecutor.CallerRunsPolicy
、ThreadPoolExecutor.DiscardPolicy
、ThreadPoolExecutor.DiscardOldestPolicy
,或自定义拒绝策略。
拒绝策略
-
AbortPolicy
(默认策略):AbortPolicy
是ThreadPoolExecutor
的默认拒绝策略。当线程池无法执行任务时,会抛出RejectedExecutionException
异常,阻止任务的执行。这个策略会快速失败,通知调用者无法接受新的任务。 -
CallerRunsPolicy
:CallerRunsPolicy
是一种简单的拒绝策略。当线程池无法执行任务时,不会抛出异常,而是将任务返回给提交任务的线程去执行。也就是说,由提交任务的线程自己来执行被拒绝的任务。这样可以降低任务提交者的速度,但也可能导致任务执行的整体速度变慢。 -
DiscardPolicy
:DiscardPolicy
是一种比较简单的拒绝策略。当线程池无法执行任务时,会默默地丢弃无法执行的任务,不做任何处理。这种策略会导致任务被静默丢弃,不会抛出异常,也无法获取到任务的执行结果。 -
DiscardOldestPolicy
:DiscardOldestPolicy
是一种相对较为复杂的拒绝策略。当线程池无法执行任务时,会丢弃最早提交的任务,然后尝试重新提交新的任务。这个策略会优先保留新提交的任务,而不是等待队列中的任务。虽然旧任务被丢弃,但可以保证新任务得到执行。
任务队列
-
ArrayBlockingQueue
:ArrayBlockingQueue
是一个基于数组的有界阻塞队列。它按照先进先出(FIFO)的顺序存储元素,并且具有固定的容量。它的构造方法需要指定队列的容量,一旦队列达到容量上限,后续的插入操作将会阻塞,直到有空间可用。同样,如果队列为空,尝试获取元素的操作也会被阻塞,直到有元素可用。ArrayBlockingQueue
是线程安全的,适用于固定大小的线程池。 -
LinkedBlockingQueue
:LinkedBlockingQueue
是一个基于链表的可选界阻塞队列。它同样按照先进先出的顺序存储元素,但是它的容量可以选择性地设置,如果不指定容量,则默认为Integer.MAX_VALUE
,即没有容量限制。与ArrayBlockingQueue
不同,LinkedBlockingQueue
在插入和获取元素时不会阻塞线程,除非队列已满或为空。LinkedBlockingQueue
同样是线程安全的,适用于可变大小的线程池。 -
SynchronousQueue
:SynchronousQueue
是一个没有存储空间的阻塞队列。每个插入操作必须等待一个相应的删除操作,反之亦然。它是一种直接传递机制,即生产者线程将元素直接交给消费者线程,而不是将元素存储在队列中。如果没有线程等待接收元素,插入操作将会阻塞,直到有消费者线程准备好接收。同样,如果没有元素可用,尝试获取元素的操作也会被阻塞,直到有生产者线程插入元素。SynchronousQueue
通常用于实现线程间的直接传递,适用于固定大小的线程池。
不同类型线程池
-
FixedThreadPool
(固定线程数线程池):FixedThreadPool
是一个固定大小的线程池,它在初始化时会创建指定数量的线程,并且线程池的大小保持不变。如果所有线程都处于活动状态,新任务将在队列中等待。FixedThreadPool
适用于需要控制并发线程数量的场景,例如限制资源使用或避免过度消耗系统资源。 -
CachedThreadPool
(缓存线程池):CachedThreadPool
是一个可根据需要自动调整大小的线程池。它会根据任务的数量动态地创建线程,如果线程闲置时间超过指定的时间(默认为60秒),则会被终止并从线程池中移除。当有新任务提交时,如果有空闲线程可用,会立即使用它来执行任务,否则会创建新线程。CachedThreadPool
适用于任务数量经常变化且需要快速处理的场景。 -
SingleThreadExecutor
(单线程线程池):SingleThreadExecutor
是一个只有一个工作线程的线程池。它按照先进先出的顺序执行任务,并且保证所有任务都在同一个线程中按顺序执行。如果该线程异常终止,会创建一个新的线程来替代它。SingleThreadExecutor
适用于需要顺序执行任务且保证任务按顺序完成的场景。 -
ScheduledThreadPool
(调度线程池):ScheduledThreadPool
是一个用于执行定时任务和周期性任务的线程池。它可以在指定的延迟时间后执行任务,也可以按指定的时间间隔周期性地执行任务。ScheduledThreadPool
适用于需要定时执行任务的场景,例如定时任务调度、定时数据备份等。