JAVA线程 -- 线程池ThreadPoolExecutor

474 阅读5分钟

线程池解决了两个不同的问题:

  1. 提升性能:它们通常在执行大量异步任务时,由于减少了每个任务的调用开销,并且它们提供了一种限制和管理资源(包括线程)的方法,使得性能提升明显;
  2. 统计信息:每个ThreadPoolExecutor保持一些基本的统计信息,例如完成的任务数量。

JDK中的实现

Java库类中提供了newCachedThreadPool、newFixedThreadPool、newSingleThreadExecutor、newScheduledThreadPool等多种线程池。

逻辑关系


  • Executor:执行器接口
  • ExecutorService:在Executor的基础上提供了执行器的生命周期管理,任务异步执行等功能
  • ScheduledExecutorService:在Excutor的基础上提供了任务的延迟执行/周期执行的功能
  • Excutors:生产具体的执行器静态工厂
  • ThreadFactory:线程工程,用于创建单个线程
  • AbstractExcutorService:ExecutorService的抽象实现,为各类执行器的实现提供基础
  • ThreadPoolExcutor:最常用的Excutor,以线程池的方式管理线程

通过Executors以静态方法的方式直接调用,实质上是它们最终调用的是ThreadPoolExecutor的构造方法。

ThreadPoolExecutor解析

下面来看看ThreadPoolExecutor的构造方法的代码

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

  • corePoolSize:线程池维护线程的最小数量。
  • maximumPoolSize:线程池维护线程的最大数量。
  • keepAliveTime:空闲线程的存活时间。
  • TimeUnit unit:时间单位,现有纳秒,微秒,毫秒,秒枚举值。
  • BlockingQueue<Runnable> workQueue:持有等待执行的任务队列,它一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列几种。
  • handler:拒绝策略;当任务太多来不及处理时,如何拒绝任务;
  • threadFactory:线程工厂,用于创建线程。

线程池对任务的处理流程

一个任务通过 execute(Runnable)方法被添加到线程池,任务就是一个 Runnable类型的对象,任务的执行方法就是Runnable类型对象的run()方法。

当一个任务通过execute(Runnable)方法欲添加到线程池时:

  • 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
  • 如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。
  • 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
  • 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。
  • 当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。

workQueue任务队列

  • Direct handoffs 直接握手队列
    Direct handoffs 的一个很好的默认选择是 SynchronousQueue,它将任务交给线程而不需要保留。这里,如果没有线程立即可用来运行它,那么排队任务的尝试将失败,因此将构建新的线程。
    此策略在处理可能具有内部依赖关系的请求集时避免锁定。Direct handoffs 通常需要无限制的maximumPoolSizes来避免拒绝新提交的任务。 但得注意,当任务持续以平均提交速度大余平均处理速度时,会导致线程数量会无限增长问题。

  • Unbounded queues 无界队列
    当所有corePoolSize线程繁忙时,使用无界队列(如LinkedBlockingQueue)将导致新任务在队列中等待,从而导致maximumPoolSize的值没有任何作用。当每个任务互不影响,完全独立于其他任务时,这可能是合适的; 例如,在网页服务器中, 这种队列方式可以用于平滑瞬时大量请求。但得注意,当任务持续以平均提交速度大余平均处理速度时,会导致队列无限增长问题。
  • Bounded queues 有界队列
    一个有界的队列(例如,一个ArrayBlockingQueue)和有限的maximumPoolSizes配置有助于防止资源耗尽,但是难以控制。队列大小和maximumPoolSizes需要相互权衡。

Rejected tasks 拒绝任务

拒绝任务有两种情况:1. 线程池已经被关闭;2. 任务队列已满且maximumPoolSizes已满;
无论哪种情况,都会调用RejectedExecutionHandler的rejectedExecution方法。预定义了四种处理策略:

  • AbortPolicy:默认测策略,抛出RejectedExecutionException运行时异常,阻止系统正常工作;
  • CallerRunsPolicy:如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行;
  • DiscardOledestPolicy:该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交;
  • DiscardPolicy:该策略会默默丢弃无法处理的任务,不予任何处理。当然使用此策略,业务场景中需允许任务的丢失;

我们可以自己定义RejectedExecutionHandler,以适应特殊的容量和队列策略场景中。

Hook methods 钩子方法

ThreadPoolExecutor为提供了每个任务执行前后提供了钩子方法,重写beforeExecute(Thread,Runnable)afterExecute(Runnable,Throwable)方法来操纵执行环境; 例如,重新初始化ThreadLocals,收集统计信息或记录日志等。此外,terminated()在Executor完全终止后需要完成后会被调用,可以重写此方法,以执行任殊处理。
注意:如果hook或回调方法抛出异常,内部的任务线程将会失败并结束

Queue maintenance 维护队列

getQueue()方法可以访问任务队列,一般用于监控和调试。绝不建议将这个方法用于其他目的。当在大量的队列任务被取消时,remove()purge()方法可用于回收空间。