本章主要学习ThreadPoolExecutor和ScheduledThreadPoolExecutor接口。
这两个类一般用于定义自定义线程池和自定义调度线程池。
关于这两个接口 ,并未进行实际编程练习,只是对其源代码注释进行总结。
1.ThreadPoolExecutor概述
@since 1.5
1.1.主要参数
ThreadPoolExecutor的最多参数的构造函数如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler){
//...
}
下面对这七个参数进行说明:
参数1:corePoolSize
- corePoolSize,即:核心线程池大小。
- 除了初始化之外,还可以通过setCorePoolSize(corePoolSize)进行动态设置。
- 核心的意思:
- 默认情况下核心线程会一直存活,即使任务执行完毕;
- 除非allowCoreThreadTimeout被设置为true,空闲线程才会因为超时而退出。
参数2:maximumPoolSize
- maximumPoolSize,即:最大线程池大小。
- 除了初始化之外,还可以通过setMaximumPoolSize(maximumPoolSize)进行动态设置。
- maximumPoolSize表示线程池的最大处理能力。
当一个新任务达到线程池时:
- 如果当前线程数量
<
corePoolSize,则创建新的线程处理新任务。 - 如果corePoolSize
<=
当前线程池数量<
maximumPoolSize,且workQueue未满,则将新任务添加到workQueue中。 - 如果corePoolSize
<=
当前线程池数量<
maximumPoolSize,且workQueue已满,则创建新线程。 - 如果当前线程数量
>=
maximumPoolSize,且workQueue未满,则将新任务添加到workQueue中。 - 如果当前线程数量
>=
maximumPoolSize,且workQueue已满,则抛出异常,拒绝任务。
参数3和4:keepAliveTime和TimeUnit
- keepAliveTime和TimeUnit,即存活时间和时间单位。
- 除了初始化之外,还可以通过setKeepAliveTime(long,TimeUnit)进行动态设置。
- 当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize。
- 如果allowCoreThreadTimeout设置为true,则所有线程均会退出直到线程数量为0。
参数5:workQueue
- workQueue ,即:待执行任务工作队列。
- 当当前线程数量
>=
corePoolSize,线程池会优先尝试将新任务放到workQueue中进行等待。 - 常用的队列有三种:
- 直传队列:SynchronousQueue,它不会持有任务,而是直接将这些任务交给线程。
- 无界队列:LinkedBlockingQueue,所有的核心线程都在忙着的时候,新来的任务会加入到队列中尽心等待
- 有界队列:ArrayBlockingQueue,使用有限的maximumPoolSize能够防止资源枯竭,但是可能增加资源的调配难度。
参数6:threadFactory
- threadFactory,即:线程工厂。
- 除了初始化之外,还可以通过setThreadFactory(aThreadFactory)进行动态设置。
- 默认情况下,ThreadPoolExecutor使用Executors#defaultThreadFactory去创建同一线程组(ThreadGroup)
的并且拥有同样优先级(NORM_PRIORITY)的非守护线程。
参数7:rejectedExecutionHandler
- rejectedExecutionHandler,即:拒绝执行任务处理器。
- 除了初始化之外,还可以通过setRejectedExecutionHandler(aRejectedExecutionHandler)进行动态设置。
- 存在以下两种新任务被拒绝的情况:
- 执行器已经关闭。
- 执行器使用有限的线程池大小和工作队列大小,并且都已经饱和。
- 常用的任务拒绝策略有四种:
- 终止策略(AbortPolicy,默认):这种策略情况下,会在拒绝任务的时候抛出一个运行期的RejectedExecutionException异常。
- 调用者自运行策略(CallerRunsPolicy):调用execute()方法的线程会自己运行这个任务。这种策略提供了一种简单的反馈控制机制,可以降低新任务提交的速率。
- 放弃策略(DiscardPolicy):如果一个任务不能被执行,那么久放弃它。
- 放弃最老任务策略(DiscardOldestPolicy):当执行器并未关闭时,位于队列头部的任务将被放弃,然后执行器会再次尝试运行execute()方法。这种再次运行可能还会失败,导致上面的步骤重复进行。
1.2.挂钩方法
ThreadPoolExecutor提供了两个protected修饰的方法,用于子类重写:
- beforeExecute(Thread, Runnable):可以在每个任务运行之前进行调用。
- afterExecute(Runnable, Throwable):可以在每个任务运行之后进行调用。
1.3.队列维护
为了监控和调试的目的,ThreadPoolExecutor一些用于方法:
- getQueue()方法:允许访问工作队列。
- remove(Runnable)方法:用于删除已经取消的某个任务,便于资源回收。
- purge()方法:用于删除已经取消的全部任务,便于资源回收。
注意:强烈禁止将上述三个方法用于其他用途。
2.ScheduledThreadPoolExecutor概述
@since 1.5
ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,它能够延时调度任务或者周期性调度任务。
当需要多个工作线程或者当需要额外的灵活性和ThreadPoolExecutor提供的额外能力时,这个类比java.util.Timer更加合适。
延时任务的执行肯定发生于它启动之后,但是如果没有指定可用的延时时间,则当任务提交时,它就会执行。
任务执行的时间与他们提交的顺序保持一致,也就是所谓的FIFO(先进先出,first-in-first-out)。
当一个提交的任务在运行前被取消了,则它的执行将被禁止。
默认情况下,这种取消了的任务并不会自动的从工作队列中移除,除非它的延时时间过期。
虽然这么做可以保证进一步的监控,但是也可能会导致取消任务的无限保留。
为了避免这么做,可以调用setRemoveOnCancelPolicy(true),此方法会使得任务一旦被取消将立即被移除。
通过scheduleAtFixedRate方法或者scheduleWithFixedDelay方法可以周期性执行一个任务,而且两种方式并不相交。
谨记一致性影响:
当不同的任务被不同的线程执行时,那些优先执行的操作结果 happens-before 那些随后执行的操作。
即:
当不同的任务被不同的线程执行时,那些优先执行的操作结果 对 那些随后执行的操作 可见。
虽然ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,但是有一些继承的方法并不适用于它。
特别要注意,作为一个固定大小的线程池,它使用了一个无界的队列,这导致maximumPoolSize这个参数并没有实际作用。
此外,千万不要将corePoolSize设置为0,或者使用allowCoreThreadTimeOut。因为这么做可能会产生一个没有线程(corePoolSize为0或超时终止)的线程池。
额外注意:
这个类重写了ThreadPoolExecutor#execute(Runnable)方法和AbstractExecutorService#submit(Runnable)方法,以此来生成内部ScheduledFuture对象,用来控制每个任务的延时和调度。
为了保护这个功能,在子类中,这些方法的重写方法都必须调用父类的版本,从而有效的禁用其他自定义任务的定制。
但是,这个类也提供了可以替代的的受保护的扩展方法decorateTask。
decorateTask可以被用来自定义具体的任务类型。
这些自定义的任务类型通过execute()方法、submit()方法、schedule()方法、(scheduleAtFixedRate)方法和(scheduleWithFixedDelay)方法来执行命令。
默认情况下,ScheduledThreadPoolExecutor使用FutureTask的任务类型。
然而,这种默认配置可以通过子类的形式进行修改和替换。
public class CustomScheduledExecutor extends ScheduledThreadPoolExecutor <font color=darkc>**
static class CustomTask<V> implements RunnableScheduledFuture<V> <font color=darkc>**
//...
**</font>
protected <V> RunnableScheduledFuture<V> decorateTask(
Runnable r, RunnableScheduledFuture<V> task) <font color=darkc>**
return new CustomTask<V>(r, task);
**</font>
protected <V> RunnableScheduledFuture<V> decorateTask(
Callable<V> c, RunnableScheduledFuture<V> task) <font color=darkc>**
return new CustomTask<V>(c, task);
**</font>
// ...
add constructors, etc.
**</font>