Java并发40:Executor系列--ThreadPoolExecutor和ScheduledThreadPoolExecutor学习笔记

本章主要学习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 

    static class CustomTask<V> implements RunnableScheduledFuture<V> 
        //...

    protected <V> RunnableScheduledFuture<V> decorateTask(
    Runnable r, RunnableScheduledFuture<V> task)
        return new CustomTask<V>(r, task);


    protected <V> RunnableScheduledFuture<V> decorateTask(
    Callable<V> c, RunnableScheduledFuture<V> task) 
        return new CustomTask<V>(c, task);

    // ...
    add constructors, etc.

 

posted @ 2021-09-24 17:43  姚春辉  阅读(58)  评论(0编辑  收藏  举报