美文网首页
ThreadPoolExecutor 的构造函数

ThreadPoolExecutor 的构造函数

作者: 竖起大拇指 | 来源:发表于2019-12-03 17:11 被阅读0次

1.构造函数

public  ThreadPoolExecutor(int corePoolSize,
        int maximumPoolSize,
        long keepAliveTime,
        TimeUnit unit,
        BlockingQueue<Runnable> workQueue,
        ThreadFactory threadFactory,
        RejectedExecutionHandler handler)

2.corePoolSize、maximumPoolSize、workQueue 三者关系

我们现在通过向线程池添加新的任务来说明着三者之间的关系。

1.如果没有空闲的线程执行该任务且当前运行的线程数少于 corePoolSize ,则添加新的线程执行该任务。

2.如果没有空闲的线程执行该任务且当前的线程数等于 corePoolSize ,同时阻塞队列未满,则将任务入队列,而不添加新的线程。

3.如果没有空闲的线程执行该任务且阻塞队列已满同时池中的线程数小于maximumPoolSize ,则创建新的线程执行任务。

4.如果没有空闲的线程执行该任务且阻塞队列已满同时池中的线程数等于maximumPoolSize ,则根据构造函数中的 handler 指定的策略来拒绝新的任务。

注意,线程池并没有标记哪个线程是核心线程,哪个是非核心线程,线程池只关心核心线程的数量。

通俗解释, 如果把线程池比作一个单位的话,corePoolSize 就表示正式工,线程就可以表示一个员工。当我们向单位委派一项工作时,如果单位发现正式工还没招满,单位就会招个正式工来完成这项工作。随着我们向这个单位委派的工作增多,即使正式工全部满了,工作还是干不完,那么单位只能按照我们新委派的工作按先后顺序将它们找个地方搁置起来,这个地方就是 workQueue ,等正式工完成了手上的工作,就到这里来取新的任务。如果不巧,年末了,各个部门都向这个单位委派任务,导致 workQueue 已经没有空位置放新的任务,于是单位决定招点临时工吧(临时工:又是我!)。临时工也不是想招多少就找多少,上级部门通过这个单位的 maximumPoolSize 确定了你这个单位的人数的最大值,换句话说最多招maximumPoolSize – corePoolSize 个临时工。当然,在线程池中,谁是正式工,谁是临时工是没有区别,完全同工同酬。

3.keepAliveTime、TimeUnit 存活时间和单位

keepAliveTime :表示空闲线程的存活时间。
TimeUnit unit :表示keepAliveTime的单位。

为了解释 keepAliveTime 的作用,我们在上述情况下做一种假设。假设线程池这个单位已经招了些临时工,但新任务没有继续增加,所以随着每个员工忙完手头的工作,都来workQueue领取新的任务(看看这个单位的员工多自觉啊)。随着各个员工齐心协力,任务越来越少,员工数没变,那么就必定有闲着没事干的员工。这样的话领导不乐意啦,但是又不能轻易fire没事干的员工,因为随时可能有新任务来,于是领导想了个办法,设定了 keepAliveTime,当空闲的员工在 keepAliveTime 这段时间还没有找到事情干,就被辞退啦,毕竟地主家也没有余粮啊!当然辞退到 corePoolSize 个员工时就不再辞退了,领导也不想当光杆司令啊!

4.workQueue 任务队列

workQueue :它决定了缓存任务的排队策略。对于不同的应用场景我们可能会采取不同的排队策略,这就需要不同类型的队列。这个队列需要一个实现了BlockingQueue接口的任务等待队列。

在ThreadPoolExecutor线程池的API文档中,一共推荐了三种等待队列,它们是:SynchronousQueue 、LinkedBlockingQueue 和 ArrayBlockingQueue

5.有限队列

SynchronousQueue :(一个不存储元素的阻塞队列)
一个 不存储元素的阻塞队列 。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于 阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法 Executors.newCachedThreadPool 使用了这个队列。

ArrayBlockingQueue:(有界阻塞队列)
一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。
新元素插入到队列的尾部, 队列获取操作则是从队列头部开始获得元素。这是一个典型的“有界缓存区”,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。一旦创建了这样的缓存区,就不能再增加其容量。试图向已满队列中放入元素会导致操作受阻塞;试图从空队列中提取元素将导致类似阻塞。

6. 无限队列

LinkedBlockingQueue:(链表结构的阻塞队列,尾部插入元素,头部取出元素)

LinkedBlockingQueue 是我们在 ThreadPoolExecutor线程池中常用的等待队列。它可以指定容量也可以不指定容量。由于它具有“无限容量”的特性,所以我还是将它归入了无限队列的范畴(实际上任何无限容量的队列/栈都是有容量的,这个容量就是Integer.MAX_VALUE)。
LinkedBlockingQueue 的实现是基于链表结构,而不是类似 ArrayBlockingQueue 那样的数组。但实际使用过程中,不需要关心它的内部实现,如果指定了LinkedBlockingQueue 的容量大小,那么它反映出来的使用特性就和 ArrayBlockingQueue 类似了。
LinkedBlockingQueue 的内部结构决定了它只能从队列尾部插入,从队列头部取出元素;

PriorityBlockingQueue (一个具有 优先级的无限阻塞队列 )

PriorityBlockingQueue 是一个按照优先级进行内部元素排序的无限队列。存放在PriorityBlockingQueue 中的元素必须实现 Comparable 接口,这样才能通过实现compareTo()方法进行排序。优先级最高的元素将始终排在队列的头部;PriorityBlockingQueue 不会保证优先级一样的元素的排序,也不保证当前队列中除了优先级最高的元素以外的元素,随时处于正确排序的位置。

Executors 线程池工具类

为了防止使用者错误搭配 ThreadPoolExecutor 构造函数的各个参数以及更加方便简洁的创建ThreadPoolExecutor对象,JavaSE中又定义了Executors类,Eexcutors类提供了创建常用配置线程池的方法。以下是Executors常用的三个创建线程池的源代码。

从源码中可以看出,Executors 间接的调用了重载的ThreadPoolExecutor 构造函数,并帮助用户根据不同的应用场景,配置不同的参数。

1.newCachedThreadPool

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0,Integer.MAX_VALUE, 
                         60L, TimeUnit.SECONDS,
                         new SynchronousQueue<Runnable>());
}

newCachedThreadPool :使用SynchronousQueue作为阻塞队列,队列无界,线程的空闲时限为60秒。这种类型的线程池非常适用IO密集的服务,因为IO请求具有密集、数量巨大、不持续、服务器端CPU等待IO响应时间长的特点。服务器端为了能提高CPU的使用率就应该为每个IO请求都创建一个线程,以免CPU因为等待IO响应而空闲

2.newFixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads){
    return new ThreadPoolExecutor(nThreads,nThreads,0L,TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

newFixedThreadPool:需指定核心线程数,核心线程数和最大线程数相同,使用LinkedBlockingQueue 作为阻塞队列,队列无界,线程空闲时间0秒。这种类型的线程池可以适用CPU密集的工作,在这种工作中CPU忙于计算而很少空闲,由于CPU能真正并发的执行的线程数是一定的(比如四核八线程),所以对于那些需要CPU进行大量计算的线程,创建的线程数超过CPU能够真正并发执行的线程数就没有太大的意义。

3.newSingleThreadExecutor

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService (
            new ThreadPoolExecutor(1,1, 0L,TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>() )
    );
}

newSingleThreadExecutor:池中只有一个线程工作,阻塞队列无界,它能保证按照任务提交的顺序来执行任务`

如何合理配置线程池

配置线程池主要从以下几个方面考虑:

  • 任务是CPU密集型,IO密集型或者混合型
  • 任务优先级,高中低
  • 任务时间执行长短
  • 任务依赖性:是否依赖其它系统资源
    cpu密集型可以配置可能小的线程,比如n+1个线程
    IO密集型可以配置较多的线程,如2n个线程
    混合型可以拆成IO密集型任务和CPU密集型任务
    可以通过Runtime.getRuntime().availableProcessors()来获取cpu个数。
    建议使用有界队列,增加系统的预警能力和稳定性。

相关文章

网友评论

      本文标题:ThreadPoolExecutor 的构造函数

      本文链接:https://www.haomeiwen.com/subject/gmxhgctx.html