专栏首页一猿小讲Executors功能如此强大,ThreadPoolExecutor功不可没(一)

Executors功能如此强大,ThreadPoolExecutor功不可没(一)

作为 Java 程序员,无论是技术面试、项目研发或者是学习框架源码,不彻底掌握 Java 多线程的知识,做不到心中有数,干啥都没底气,尤其是技术深究时往往略显发憷。

在 JDK1.5 以前,研发人员在面对线程频繁调度的场景,必须手动打造线程池,来节约系统开销(画外音:真是吃了不少苦头)。

从 JDK1.5 开始,Java 提供了一个 Excutors 工厂类来生产线程池,可以帮助研发人员有效的进行线程控制(画外音:不用造轮子啦,爽歪歪)。

(配图释义:JDK 1.8 能用 Excutors 创建的线程池)

如上图示意,Excutors 提供了满足各种场景的线程池创建方式, Java 研发人员就不用苦逼哈哈的去造轮子啦,谁用谁爽。

但是,**开发规约明确强制研发人员:线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式。

(配图释义:****Java开发手册,线程池创建规约)

不过,若经常关注源码的同学会发现,无论是 newFixedThreadPool() 方法、newSingleThreadExecutor() 方法,还是 newCachedThreadPool() 方法,其背后均使用了 ThreadPoolExecutor。

(配图释义:JDK 1.8 能用 Excutors 创建的线程池的背后)

通过上面源码截图,可以清晰看出,以上几种创建线程池的方式,均是对 ThreadPoolExecutor 类的封装,所以要想彻底掌握线程池,势必要吃透线程池背后的 ThreadPoolExecutor。

1

解剖:构造函数

有关 ThreadPoolExecute 构造函数,很多书上或者文章都会提到,下面再简单了解一下每个参数的具体含义。

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

构造函数的参数释义:

corePoolSize:指定线程池中的线程数量; maximumPoolSize:指定线程池中的最大线程数量; keepAliveTime:当线程池中线程数量超过 corePoolSize 时,空闲线程的存活时间; unit:keepAliveTime 的单位; workQueue:任务队列,存放提交尚未被执行的任务; threadFactory:线程工厂,用于创建线程,一般用默认的即可; handler:拒绝策略,当任务太多来不及处理,如何拒绝任务。

以上参数除了 workQueue 以及 handler 外,大部分都很易懂。接下来重点说说 workQueue 以及 handler 两个参数。

参数 BlockingQueue<Runnable> workQueue,是用于存放提交尚未被执行的任务的队列,类型是 BlockingQueue 接口的对象,用于存放 Runnable 对象。

参数 RejectedExecutionHandler handler 是指当任务数量超过系统承载能力时,该如何处理?其中 JDK 提供了四种拒绝策略。

(配图释义:JDK 1.8 内置的拒绝策略)

JDK 提供的四种拒绝策略归纳,简单了解一下。

2

思考:使用 Executors 会导致 OOM?

了解完 ThreadPoolExecutor 类的构造函数,接下来探讨一下**开发手册明确强制的一条使用线程池的规约。

为了更清晰的认识,不妨走进源码看一看。首先走进 newFixedThreadPool() 方法的源码,一探究竟。

如源码截图所示,newFixedThreadPool() 方法的实现,返回一个 corePoolSize 和 maximumPoolSize 大小一样的,并且使用了 LinkedBlockingQueue 任务队列的线程池。

如上面 LinkedBlockingQueue 的源码所示,队列的默认长度为 Integer.MAX_VALUE,那么当任务提交频繁时,线程池中的线程处理不过来时,队列可能会迅速膨胀,从而会出现 OOM。

接着走进 newSingleThreadExecutor() 方法的源码,看看有没有新大陆。

如源码截图示意,newSingleThreadExecutor 方法实现中,corePoolSize 和 maximumPoolSize 设置的值均为 1,返回一个单线程的线程池,并且使用 LinkedBlockingQueue 任务队列来存在提交的任务,与 newFixedThreadPool() 方法一样,当任务提交频繁时,线程池中的线程处理不过来时,队列会迅速膨胀,从而会出现 OOM。

最后看看 newCachedThreadPool() 方法的源码实现,一探究竟。

如上图源码示意,newCachedThreadPool() 方法实现,返回了一个 corePoolSize 为 0,maximumPoolSize 的值为 Integer.MAX_VALUE,并且使用 SynchronousQueue 作为任务队列的线程池。

而 SynchronousQueue 队列是一种直接提交的队列(不会保存提交的任务),所以总会使线程池增加新的线程来执行任务,当任务执行完毕后,由于 corePoolSize 为 0,因此空闲线程又会在 60 秒内被回收。

如果同时有大量任务被提交,而任务的执行又不那么快时,newCachedThreadPool() 方法,便会开启大量的线程进行处理,这样可能很快耗尽系统的资源,进而导致 OOM。

3

寄语写最后

本次,主要引入线程池背后的 ThreadPoolExecutor 类,算是正式开启探寻线程池背后的奥秘之旅,先有个初步的认识,知其然知其所以然,后续会逐步深入。

好了,本次就谈到这里,一起聊技术、谈业务、喷架构,少走弯路,不踩大坑。会持续输出原创精彩分享,敬请期待!

本文分享自微信公众号 - 一猿小讲(yiyuanxiaojiangV5),作者:一猿小讲

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-08-03

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Spring Boot 1.X和2.X优雅重启实战

    项目在重新发布的过程中,如果有的请求时间比较长,还没执行完成,此时重启的话就会导致请求中断,影响业务功能,优雅重启可以保证在停止的时候,不接收外部的新的请求,等...

    用户5224393
  • Spring Boot 优雅重启知多少

    项目在重新发布的过程中,如果有的请求处理时间比较长,还没执行完成,此时重启的话就会导致请求中断,影响业务功能,优雅重启可以保证在停止的时候,不接收外部的新的请求...

    Bug开发工程师
  • Spring Boot 1.X和2.X优雅重启实战

    项目在重新发布的过程中,如果有的请求时间比较长,还没执行完成,此时重启的话就会导致请求中断,影响业务功能,优雅重启可以保证在停止的时候,不接收外部的新的请求,等...

    纯洁的微笑
  • 高并发之——创建线程池居然有这么多方式...

    作者个人研发的在高并发场景下,提供的简单、稳定、可扩展的延迟消息队列框架,具有精准的定时任务和延迟队列处理功能。自开源半年多以来,已成功为十几家中小型企业提供了...

    冰河
  • Spring Boot 1.X和2.X优雅重启实战

    在重启之前首先发送重启命令到endpoint,或者用kill 进程ID的方式,千万不要用kill -9。

    猿天地
  • 线程池

    如果要让线程池执行任务,需要实现的 Runnable 接口或 Callable 接口。Runnable 接口或 Callable 接口的实现类都可以被 Thre...

    happyJared
  • 线程池之小结

    多线程:解决多任务同时执行的需求,合理使用CPU资源。多线程的运行是根据CPU切换完成,如何切换由CPU决定,因此多线程运行具有不确定性。

    蜻蜓队长
  • 被开发者抛弃的 Executors,错在哪儿?

    在 Java 领域内,我们使用多线程的方式来实现并发编程。而线程本身是操作系统的一个概念,虽然不同的语言对线程都进行了一些封装,但是最终都是调用到操作系统中去创...

    Java_老男孩
  • Java面试高频问题汇总 线程池专题

    线程池提供了一种限制和管理资源(包括执行一个任务)。每个线程池还维护一些基本统计信息,例如已完成的任务数量。

    Steve Wang
  • 【小家java】Java中的线程池,你真的用对了吗?(教你用正确的姿势使用线程池,Executors使用中的坑)

    在【小家java】用 ThreadPoolExecutor/ThreadPoolTaskExecutor 线程池技术提高系统吞吐量(附带线程池参数详解和使用注意...

    YourBatman
  • java-线程池(ThreadPoolExecutor)的参数解析

    很多时候为了省事用的都是Executors的方式去创建,感觉也没什么问题,不过阿里工程师的推荐自然是有道理的,以后还是尽量改用ThreadPoolExecuto...

    李林LiLin
  • 线程池

    不光是线程池,池化思想在诸多地方有着很好的应用,比如对象池、连接池等等。。一般运用池化思想的都是一些比较消耗系统资源的操作,通过池化,可以降低内存消耗,并且可以...

    AlbertZhang
  • 阿里面试官鬼得很,问我为什么他们阿里要禁用Executors创建线程池?

    看阿里巴巴开发手册并发编程这块有一条:线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,通过源码分析禁用的原因。

    乔戈里
  • ​Executors源码学习

    在学习并发线程池的时候,我们基本都已经学习了一下常见的线程。但是我们发现Executors这样一个类的存在。那么这个类是干什么的?其实在分析ThreadPool...

    写一点笔记
  • 如何优雅的使用线程池!!!

    在前面使用的例子用,我们已经使用过线程池,基本上就是初始化线程池实例之后,把任务丢进去,等待调度执行就可以了,使用起来非常简单、方便。虽然使用很简单,但线程池涉...

    用户2242639
  • 用了10年Postman,没想到它的Mock功能也如此强大

    在整个开发过程中,前端或后端的延迟可能会阻碍相关团队有效地完成工作。一些后端的API工程师已经开始使用Postman去测试后端endpoint,而不依赖于前端U...

    吾非同
  • 多线程编程学习五(线程池的创建)

    一、概述 New Thread的弊端如下:        a、每次New Thread新建对象性能差。        b、线程缺乏统一的管理,可能无限制的新建...

    JMCui
  • 线程池原理(1)

    池化技术相比大家已经屡见不鲜了,线程池、数据库连接池、Http 连接池等等都是对这个思想的应用。池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用...

    黑洞代码
  • Java并发编程之线程池

    java.util.concurrent.Executors提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池

    日薪月亿

扫码关注云+社区

领取腾讯云代金券