Java ThreadPoolExecutor 线程池调度器

来源:互联网 发布:java从小白到大牛 编辑:程序博客网 时间:2024/06/05 03:35

可设置的主要参数


  • corePoolSize

核心线程数,核心线程会一直存活,即使没有任务需要处理。当线程数小于核心线程数时,即使现有的线程空闲,线程池也会优先创建新线程来处理任务,而不是直接交给现有的线程处理。

核心线程在allowCoreThreadTimeout被设置为true时会超时退出,默认情况下不会退出。

  • maxPoolSize

当线程数大于或等于核心线程,且任务队列已满时,线程池会创建新的线程,直到线程数量达到maxPoolSize。如果线程数已等于maxPoolSize,且任务队列已满,则已超出线程池的处理能力,线程池默认会拒绝处理任务而抛出异常。可以自定义处理多余任务的RejectedExecutionHandler

该参数在某些情况下是无效的。

  • keepAliveTime

当已经执行过的线程空闲后,空闲时间小于keepAliveTime,又来了新的任务时,会直接启动该线程。

当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize。

如果allowCoreThreadTimeout设置为true,则所有线程均会退出直到线程数量为0。

  • allowCoreThreadTimeout

是否允许核心线程空闲退出,默认值为false。

  • queueCapacity

任务队列容量,由它设定一个有界的队列。从maxPoolSize的描述上可以看出,任务队列的容量会影响到线程的变化,因此任务队列的长度也需要恰当的设置。

  • RejectedExecutionHandler

    public interface RejectedExecutionHandler {  void rejectedExecution(Runnable r, ThreadPoolExecutor executor);}

    任务拒绝处理策略。在源码中定义了如下几种策略:

    • AbortPolicy (默认策略)
    public static class AbortPolicy implements RejectedExecutionHandler {        public AbortPolicy() { }        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {            throw new RejectedExecutionException("Task " + r.toString() +                                                 " rejected from " +                                                 e.toString());        }    }

    该策略 直接抛出了异常。异常中止

    • CallerRunsPolicy
    public static class CallerRunsPolicy implements RejectedExecutionHandler {    public CallerRunsPolicy() { }    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {        if (!e.isShutdown()) {            r.run();        }    }}

    在线程池未关闭时,直接在当前线程执行任务。所谓当前线程,即提交任务时的线程

    • DiscardPolicy
    public static class DiscardPolicy implements RejectedExecutionHandler {    public DiscardPolicy() { }    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {    }}

    不做任何处理,即跳过任务r

    • DiscardOldestPolicy
    public static class DiscardOldestPolicy implements RejectedExecutionHandler {    public DiscardOldestPolicy() { }    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {        if (!e.isShutdown()) {            e.getQueue().poll();            e.execute(r);        }    }}

    在线程池未关闭时,移除处于队头的任务,再执行任务r。相当于把之前队头的任务跳过了,把r又加入了队列

 

参数对线程池任务调度的影响


当任务数大于等于corePoolSize,这时,除核心线程执行了一定数量的任务后,

余下的需要执行的任务数为int count = tasksCount - corePoolSize,设队列最大容量为capacity:

  • capacity >= count,则只会创建corePoolSize个线程来执行任务。多的任务放在队列中。

    这时maxPoolSize,就无效了

  • capacity < count,这时有int remain = tasksCount - capacity

    • remain <= maxPoolSize,创建remain个线程来执行任务。多的任务放在队列中
    • remain > maxPoolSize,创建maxPoolSize个线程来执行任务;这时将有remain-maxPoolSize个任务无法加入队列,被RejectedExecutionHandler处理。

 

Executors生成ThreadPoolExecutor


(most from http://dongxuan.iteye.com/blog/901689)

在构建时,corePoolSize、maximumPoolSizes 和BlockingQueue的选择,直接影响线程调度的策略

Executors 有三个方法可以生成 ThreadPoolExecutor

生成一个固定线程数的 ThreadPoolExecutor

public static ExecutorService newFixedThreadPool(int nThreads) {

​ return new ThreadPoolExecutor(nThreads, nThreads,

​ 0L, TimeUnit.MILLISECONDS,

​ new LinkedBlockingQueue());

}

LinkedBlockingQueue实现是线程安全的,实现了先进先出等特性,是作为生产者消费者的首选,LinkedBlockingQueue 可以指定容量,也可以不指定,不指定的话,默认最大是Integer.MAX_VALUE,其中主要用到put和take方法,put方法在队列满的时候会阻塞直到有队列成员被消费,take方法在队列空的时候会阻塞,直到有队列成员被放进来。在ThreadPoolExecutor中主要使用的是BlockingQueue的offer()和 poll()、take()

LinkedBlockingQueue 无界队列

生成一个只含一个线程的ThreadPoolExecutor

public static ExecutorService newSingleThreadExecutor() {

​ return new FinalizableDelegatedExecutorService

​ (new ThreadPoolExecutor(1, 1,

​ 0L, TimeUnit.MILLISECONDS,

​ new LinkedBlockingQueue()));

}

LinkedBlockingQueue 无界队列

生成一个无界的并直接提交的ThreadPoolExecutor

public static ExecutorService newCachedThreadPool() {

​ return new ThreadPoolExecutor(0, Integer.MAX_VALUE,

​ 60L, TimeUnit.SECONDS,

​ new SynchronousQueue());

}

这个线程池根据需要(新任务到来时)创建新的线程,如果有空闲线程则会重复使用,线程空闲了60秒后会被回收

SynchronousQueue 的特点是,每个插入操作必须等待另一个线程的对应移除操作。比如,要添加一个元素,接下来如果继续想尝试添加则会阻塞,直到另一个线程取走一个元素,反之亦然

SynchronousQueue 不会保留任务进队列,会直接提交到工作线程。

如果当前没有可工作线程,那么在 corePoolSize

关于工作队列(workQueue):

所有 BlockingQueue 都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互:

  • 如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。(什么意思?如果当前运行的线程小于corePoolSize,则任务根本不会存放,添加到queue中,而是直接开始运行thread。
  • 如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。
  • 如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。

排队有三种通用策略:

  1. 直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

  2. 无界队列,即队列容量超出余下任务量。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

  3. 有界队列,即队列容量小于余下任务量。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue或具有预定义容量的 LinkedBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
     

示例


package com.stone.threadpool;import java.util.concurrent.*;/** * Created by stone on 10/05/2017. */public class Test {    public static void main(String[] args) {        ExecutorService pool = newThreadPool(2);        for (int i = 0; i < 11; i++) {            final int index = i;            pool.execute(()-> {                System.out.println(Thread.currentThread().getName() + " indexA=" + index);                try {                    Thread.sleep(2000);                } catch (InterruptedException e) {                    e.printStackTrace();                }//                System.out.println(Thread.currentThread().getName() + " indexB=" + index);            });        }    }    public static ExecutorService newThreadPool(int nThreads) {//        return newFixedThreadPool(nThreads);//        return newSingleThreadPool();//        return newCachedThreadPool();        return newLimitThreadPoll();    }    //生成一个固定线程数的 ThreadPoolExecutor    public static ExecutorService newFixedThreadPool(int nThreads) {//        return new ThreadPoolExecutor(nThreads, nThreads,        return new ThreadPoolExecutor(nThreads, nThreads * 3, //这时 maximumPoolSize没什么用                0, TimeUnit.MILLISECONDS,                new LinkedBlockingQueue<>()); //默认队列容量为Integer.MAX_VALUE    }    //生成一个只含一个线程的ThreadPoolExecutor    public static ExecutorService newSingleThreadPool() {        return new ThreadPoolExecutor(1, 1, //这里增大maximumPoolSize 也是没用的                        0L, TimeUnit.MILLISECONDS,                        new LinkedBlockingQueue<Runnable>());//默认队列容量为Integer.MAX_VALUE    }    //生成一个无界的并使用缓存的ThreadPoolExecutor    public static ExecutorService newCachedThreadPool() {        return new ThreadPoolExecutor(2, Integer.MAX_VALUE,                60L, TimeUnit.SECONDS,                new SynchronousQueue<Runnable>());//默认队列容量为Integer.MAX_VALUE    }    public static ExecutorService newLimitThreadPoll() {        return new ThreadPoolExecutor(2, 5,                0l, TimeUnit.SECONDS,//                new ArrayBlockingQueue<Runnable>(5));                new LinkedBlockingQueue<>(5),//                new ThreadPoolExecutor.AbortPolicy()); //异常中止//                new ThreadPoolExecutor.CallerRunsPolicy()); //在当前线程直接执行//                new ThreadPoolExecutor.DiscardPolicy()); //空实现,跳过任务                new ThreadPoolExecutor.DiscardOldestPolicy()); //移除队头,由pool执行    }}