白话ThreadPoolExecutor

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

参数释义

  1. corePoolSize:

    重要核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;

  2. maximumPoolSize:
    线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;

  3. keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime开始起作用,一个线程空闲的时间达到keepAliveTime则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;

  4. unit:参数keepAliveTime的时间单位

  5. workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
    一般使用其中3个:
    SynchronousQueue(无内部容量同步队列)
    ArrayBlockingQueue (指定容量的数组阻塞队列)
    LinkedBlockingQueue( 链表阻塞队列)

  6. threadFactory:线程工厂,主要用来创建线程;

  7. handler:表示当拒绝处理任务时的策略,默认有以下四种取值:

    ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

    ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。

    ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

    ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

围绕3个重要参数来讲解:

第一先讲执行逻辑,主要围绕coreSize, maxSize来讲述它的设计:

当任务提交给ThreadPoolExecutor 线程池中,先检查核心线程数是否已经全部使用,如果没有交由核心线程去执行任务;如果核心线程数已经全部占用,则将任务添加到队列里面;如果队列已经占满,比较当前线程池的中线程的数量是不是已经超过maximumPoolSize,如果没有超过则创建非核心线程去执行,会产生后面的这部分任务会比中间这部分在队列中的先执行;如果超过了maximumPoolSize,则执行拒绝策略Policy。

  • 线程池最多可以接受多少任务呢?就是maximumPoolSize+队列的大小。当线程池中的线程的数量大于corePoolSize数量有空闲线程则执行回收,回收时间是keepAliveTime,单位是unit,都是初始化的时候设置的。
    在这里插入图片描述

第二,就来研究下不同的队列对线程池的运行逻辑的影响。

  1. ArrayBlockingQueue (指定容量的数组阻塞队列)

他内部维护的是一个数组,就是你传入的capcity的大小。使用ArrayBlockingQueue有界任务队列,若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,会将新的任务加入到等待队列中。这里等待队列的任务数量设置为比如3,若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize,则执行拒绝策略。

ExecutorService pool = new ThreadPoolExecutor(1, 3, 1000, TimeUnit.MILLISECONDS, 
new ArrayBlockingQueue<Runnable>(3), 
Executors.defaultThreadFactory(), 
new ThreadPoolExecutor.AbortPolicy());

这就是完全符合我们图片中的逻辑。这么看下来,大白话地说,他适合,并发数量较为可控,核心线程也需要保留几个常驻的执行执行,偶尔多点任务吧,也不用多太多,这种情况。

  1. SynchronousQueue (无内部容量同步队列)

SynchronousQueue是一个特殊的BlockingQueue,它没有容量,每执行一个插入操作就会阻塞。

用这个blockingQueue的话,当core线程已经占满正在处理的时候,再来新任务,意味着图中的“队列是否满了”的判断,一直是满的,进而需要开辟新线程(max没超过的情况下,否则就拒绝策略了)。所以就要求你的最大线程数要大一点,所以一般来讲,SynchronousQueue需要搭配maximumPoolSize Integer.MAX_VALUE。参考okhttp中的代码:

executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 
60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), 
Util.threadFactory("OkHttp Dispatcher", false));

其实与Executors.java几乎一样:

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

他的注释:创建一个线程池,该线程池根据需要创建新线程,并且会重用以前构造的可用的线程(补充,即还没超时,没有回收的)。它通常会提高执行许多短期异步任务的程序的性能。调用execute将重用以前构造的线程(如果可用)。如果没有可用的现有线程,将创建一个新线程并将其添加到池中。六十秒未使用的线程将被终止并从缓存中删除。因此,空闲时间足够长的池不会消耗任何资源。

所以这么写的目的,是为了3个目的:

  • 无限并发(maxSize = Integer.MAX_VALUE实现的);

  • 全部执行完成后,过一段时间后能释放所有的资源,core为0;

  • 如果短时间内并发多,那么他就能复用还没到期的线程。这就对应着api中描述的“These pools will typically improve the performance of programs that execute many short-lived asynchronous tasks”。

大白话来说,就是并发网络请求,比如刷手机图片看动漫,说不定,我就在疯狂的滑动刷新,短时间内就需要拉取几十上百张图片;说不定,我后面半个小时不拉图了,我想释放资源节约内存。那么这个模式就十分适合。如果还用ArrayBlockingQueue就会有溢出的;或者排队的太多。

  1. LinkedBlockingQueue( 链表阻塞队列)

他与1比较,他是无界的。他内部是用链表实现,简单讲,就是无限往下挂节点,是无限的。因此,在图中的“队列是否满了”的判断,他永远满不了。与2完全相反。使用无界的任务队列,线程池的任务队列可以无限制添加新任务,而线程池创建的最大线程数就是corePoolSize设置的数量,这种情况下maximumPoolSize设置的值是不会生效的,就算等待队列中有许多未执行的任务,线程池的数量达到了corePoolSize的值后也不会增加新的线程。

ExecutorService pool = new ThreadPoolExecutor(1, 3, 1000, 
TimeUnit.MILLISECONDS, 
new LinkedBlockingQueue<Runnable>(),
Executors.defaultThreadFactory(), 
new ThreadPoolExecutor.AbortPolicy());

大白话:他的场景:去某些部门办事情,总共10(maxSize)个窗口,就3(coreSize)个窗口, 而剩下那些窗口永远也没有工作人员来服务。你们就取号以后乖乖排队(LinkedBlockingQueue)吧,等着1,2,3窗口(线程)叫号执行。它也不会拒绝你,除非shutdownNow()抛出异常,shutdown()会等待干完。

  • 注意,如果LinkedBlockingQueue给了限定的capcity则会变成ArrayBlockingQueue一样的逻辑

========================

最后,补充最近遇到一个问题,使用Executors.newXXX创建的无法抓取到异常日志。网上很多解决方案。
总结下来4个点,
1。submit()的Future,使用future.get()去阻塞,抛异常;
2。execute()的,自己try catch(Throwable),自己的代码,比较不优雅;
3。给线程池new出来的Thread设置handler,传入ThreadFactory,给新创建的Thread setUncaughtExceptionHandler;
4。ScheduledThreadPoolExecutor 无法被3给处理。因为在ScheduledThreadPoolExecutor的内部代码做了一个try finally。只能在外部自行catch,并且是throwable,而不是Exception。

=================================

如上分析3种特性的blockingQueue以后,我们再来看其他的blockingQueue。

LinkedTransferQueue:LinkedBlockingDeque, 表现基本与linkedBlockingQueue差不多。

PriorityBlockingQueue: 一个支持优先级排序的无界阻塞队列,对元素没有要求,可以实现Comparable接口也可以提供Comparator来对队列中的元素进行比较,跟时间没有任何关系,仅仅是按照优先级取任务。

DelayQueue: 同PriorityBlockingQueue,也是二叉堆实现的优先级主阻塞队列。要求元素都实现Delayed接口,通过执行时延从队列中提取任务,时间没到任务取不出来。就变成ScheduledThreadPoolExecutor。有空可以再说说。相信理解了三大基础的逻辑就好理解了。

补充下,是如何回收线程的todo。

ThreadPoolExecutor——线程销毁 - FFStayF - 博客园 (cnblogs.com)

还有就是拒绝策略todo。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值