Java线程池ThreadPoolExecutor初略探索

在操做系统中,线程是一个很是重要的资源,频繁建立和销毁大量线程会大大下降系统性能。Java线程池原理相似于数据库链接池,目的就是帮助咱们实现线程复用,减小频繁建立和销毁线程
ThreadPoolExecutor图解java

ThreadPoolExecutor

ThreadPoolExecutor是线程池的核心类。首先看一下如何建立一个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;
}

使用案例并发

/**
  * 阻塞的线程池
  */
private ThreadPoolExecutor executor = new ThreadPoolExecutor(
    0,      // corePoolSize:线程池维护线程的最少数量
    4,      // maximumPoolSize:线程池维护线程的最大数量
    10000,  // keepAliveTime:线程池维护线程所容许的空闲时间
    TimeUnit.MILLISECONDS,          // unite:线程池维护线程所容许的空闲时间的单位
    new LinkedBlockingQueue<>(200), // workQueue:线程池所使用的缓冲队列
    new CallerBlockedPolicy()       // handler:线程池对拒绝任务的处理策略,自定义拓展
  );

构造方法参数说明

  • corePoolSize:核心线程数量,当有新任务在execute()方法提交时,会执行如下判断:
    • 若是运行的线程少于 corePoolSize,则建立新线程来处理任务,即便线程池中的其余线程是空闲的;
    • 若是线程池中的线程数量大于等于 corePoolSize 且小于 maximumPoolSize,则只有当workQueue满时才建立新的线程去处理任务;
    • 若是设置的corePoolSizemaximumPoolSize相同,则建立的线程池的大小是固定的,这时若是有新任务提交,若workQueue未满,则将请求放入workQueue中,等待有空闲的线程去从workQueue中取任务并处理;
    • 若是运行的线程数量大于等于maximumPoolSize,这时若是workQueue已经满了,则经过handler所指定的策略来处理任务;
      因此,任务提交时,判断的顺序为 corePoolSize –> workQueue –> maximumPoolSize。
  • maximumPoolSize:最大线程数量;
  • workQueue:等待队列,当任务提交时,若是线程池中的线程数量大于等于corePoolSize的时候,把该任务封装成一个Worker对象放入等待队列;
  • workQueue:保存等待执行的任务的阻塞队列,当提交一个新的任务到线程池之后, 线程池会根据当前线程池中正在运行着的线程的数量来决定对该任务的处理方式,主要有如下几种处理方式:
    • 直接切换:这种方式经常使用的队列是SynchronousQueue,但如今尚未研究过该队列,这里暂时还无法介绍;
    • 使用无界队列:通常使用基于链表的阻塞队列LinkedBlockingQueue。若是使用这种方式,那么线程池中可以建立的最大线程数就是corePoolSize,而maximumPoolSize就不会起做用了(后面也会说到)。当线程池中全部的核心线程都是RUNNING状态时,这时一个新的任务提交就会放入等待队列中。
    • 使用有界队列:通常使用ArrayBlockingQueue。使用该方式能够将线程池的最大线程数量限制为maximumPoolSize,这样可以下降资源的消耗,但同时这种方式也使得线程池对线程的调度变得更困难,由于线程池和队列的容量都是有限的值,因此要想使线程池处理任务的吞吐率达到一个相对合理的范围,又想使线程调度相对简单,而且还要尽量的下降线程池对资源的消耗,就须要合理的设置这两个数量。
      • 若是要想下降系统资源的消耗(包括CPU的使用率,操做系统资源的消耗,上下文环境切换的开销等), 能够设置较大的队列容量和较小的线程池容量, 但这样也会下降线程处理任务的吞吐量。
      • 若是提交的任务常常发生阻塞,那么能够考虑经过调用 setMaximumPoolSize() 方法来从新设定线程池的容量。
      • 若是队列的容量设置的较小,一般须要将线程池的容量设置大一点,这样CPU的使用率会相对的高一些。但若是线程池的容量设置的过大,则在提交的任务数量太多的状况下,并发量会增长,那么线程之间的调度就是一个要考虑的问题,由于这样反而有可能下降处理任务的吞吐量。
  • keepAliveTime:线程池维护线程所容许的空闲时间。当线程池中的线程数量大于corePoolSize的时候,若是这时没有新的任务提交,核心线程外的线程不会当即销毁,而是会等待,直到等待的时间超过了keepAliveTime
  • threadFactory:它是ThreadFactory类型的变量,用来建立新线程。默认使用Executors.defaultThreadFactory() 来建立线程。使用默认的- - - - ThreadFactory来建立线程时,会使新建立的线程具备相同的NORM_PRIORITY优先级而且是非守护线程,同时也设置了线程的名称。
  • handler:它是RejectedExecutionHandler类型的变量,表示线程池的饱和策略。若是阻塞队列满了而且没有空闲的线程,这时若是继续提交任务,就须要采起一种策略处理该任务。线程池提供了4种策略:
    • AbortPolicy:直接抛出异常,这是默认策略;
    • CallerRunsPolicy:用调用者所在的线程来执行任务;
    • DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
    • DiscardPolicy:直接丢弃任务;

线程池的监控

经过线程池提供的参数进行监控。线程池里有一些属性在监控线程池的时候可使用性能

  • getTaskCount:线程池已经执行的和未执行的任务总数;
  • getCompletedTaskCount:线程池已完成的任务数量,该值小于等于taskCount;
  • getLargestPoolSize:线程池曾经建立过的最大线程数量。经过这个数据能够知道线程池是否满过,也就是达到了 maximumPoolSize;
  • getPoolSize:线程池当前的线程数量;
  • getActiveCount:当前线程池中正在执行任务的线程数量。

总结一下线程池添加任务的整个流程:this

  1. 线程池刚刚建立是,线程数量为0;
  2. 执行execute添加新的任务时会在线程池建立一个新的线程;
  3. 当线程数量达到corePoolSize时,再添加新任务则会将任务放到workQueue队列;
  4. 当队列已满放不下新的任务,再添加新任务则会继续建立新线程,但线程数量不超过maximumPoolSize;
  5. 当线程数量达到maximumPoolSize时,再添加新任务则会抛出异常。
相关文章
相关标签/搜索