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

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

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


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


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


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


可是,阿里开发规约明确强制研发人员:线程池不容许使用 Executors 去建立,而是经过 ThreadPoolExecutor 的方式多线程


配图释义:阿里巴巴Java开发手册,线程池建立规约架构


不过,若常常关注源码的同窗会发现,不管是 newFixedThreadPool() 方法newSingleThreadExecutor() 方法,仍是 newCachedThreadPool() 方法,其背后均使用了 ThreadPoolExecutor。app


(配图释义: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 类,算是正式开启探寻线程池背后的奥秘之旅,有个初步的认识,知其然知其因此然,后续会逐步深刻。

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

推荐阅读:
完全搞懂 Java 线程池,干啥都再也不发憷
Java程序跑的快,全要靠线程带
fastjson的这些坑,你误入了没?
真实|技术人员该如何站好最后一班岗?
Java 8 的这些特性,你知道吗?
改掉这些坏习惯,还怕写不出健壮的代码?(一)
改掉这些坏习惯,还怕写不出优雅的代码? (二)
改掉这些坏习惯,还怕写不出优雅的代码? (三)
改掉这些坏习惯,还怕写不出健壮的代码? (四)
改掉这些坏习惯,还怕写不出精简的代码? (五)
改掉这些坏习惯,还怕写不出精简的代码? (六)
坚持是一种信仰,在看是一种态度!

本文分享自微信公众号 - 一猿小讲(yiyuanxiaojiangV5)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索