Java线程池配置由繁至简,找到适合自己的天命线程池(一)
前提知识 🧀
还记得刚入这行,还处于实习阶段的我,第一个项目就震撼到我了,因为发现自己熬夜苦读学习的知识和实际工作中需要的差别太大了,再加上项目用到的一些框架模块都很久,我连阅读代码的业务逻辑都很困难;其中让我印象深刻的就有一个封装了群发 http 请求的工具类,里面就用到了线程池,眼花缭乱的参数让那时的我头痛不已,有的参数甚至不知道是做什么用,为什么要设置成这个?
时间是让人猝不及防的东西,这么久终画上句。
免不了认识的 7 个基本参数
1 | public ThreadPoolExecutor(int corePoolSize, |
贴上源码里的注释 并附加一些个人向的补充(参数加星代表比较重要):
*corePoolSize - 线程池中保留的线程数,即使它们处于空闲状态,除非设置 allowCoreThreadTimeOut 。
核心线程相当于合同工,有活儿干活儿,没活儿也得呆着,这个参数代表合同工(核心线程)的数量,int 型
*maximumPoolSize - 池中允许的最大线程数 。
除了合同工,在有大量的工作堆积时,还可以找一些临时工来帮忙,这个参数代表总员工(合同工和临时工)数量的上限,int 型
keepAliveTime - 当线程数大于核心时,这是多余的空闲线程在终止前等待新任务的最长时间。
临时工在没活儿的时候就遣散,这个参数代表多长时间没活干就遣散(销毁空闲线程),long 型
unit – keepAliveTime 参数的时间单位 。
上面 keepAliveTime 参数的单位,在 TimeUnit 枚举中选择即可
*workQueue – 用于在执行任务之前保存任务的队列。(后面这句不用深究,可以不看)此队列将仅保存由 execute 方法提交的 Runnable 任务。
任务一直派,员工们干不过来,就设置一个队列存着这些任务;有好多种,下面会详细介绍
threadFactory – 执行程序创建新线程时使用的工厂 。
可以在这里给员工(线程)们命名之类的
*handler – 由于达到线程边界和队列容量而阻塞执行时使用的处理程序。
大多数文章会把它叫做拒绝策略,直译过来确实也没毛病,但新接触的人可能因为翻译的原因产生歧义;完整的含义是因为队列饱和所采用的处理程序:可能是拒绝,可能是丢弃,甚至可能不拒绝,会新建个线程继续跑任务,所以我们后面会沿用饱和策略的称呼,大家知道这两个称呼是同一个意思即可。
几个重要参数的要求和相互之间的逻辑关系
如果以下其中一项成立,将会抛出 IllegalArgumentException
- corePoolSize < 0
- keepAliveTime < 0
- maximumPoolSize <= 0
- maximumPoolSize < corePoolSize
上面是比较常规的要求,一句话说就是最大线程至少为 1,并且要大于核心线程数量。
threadFactory 和 handler 不是必填参数,两者都会有默认值,所以一些构造方法可能只用到其他 5 个参数。
常用的几个任务队列
为了更清晰地认识线程池,我们要大致介绍一下:
- ArrayBlockingQueue
看到 Array 开头,我们就知道这个队列是使用数组实现的队列。
- LinkedBlockingQueue
这个以 Linked 开头,大家比较熟悉以此开头的有 LinkedList,其实这个队列就是用链表实现的队列。
有的文章会把 ArrayBlockingQueue 叫做有界队列,把 LinkedBlockingQueue 叫做无界队列,对此我只想说:有一点误导人。
因为两者说白只有底层实现不同,我们知道数组在内存是连续的,所以需要规定大小,链表可以不连续,所以理论上可以无限延长,但也不代表就一定是无界的。
LinkedBlockingQueue 有一个参数叫 capacity,就是代表队列的容量,无界的原因是用了无参构造,capacity 就默认为 Integer.MAX_VALUE,但就像 list 和 map 一样,你可以在一开始就设置你想要的容量。
1 | //无参构造 |
这样横向一比较,LinkedBlockingQueue 的吞吐量比 ArrayBlockingQueue 要高,可以跟 ArrayBlockingQueue 一样规定最大容量,也可以无界;这么一比,LinkedBlockingQueue 完胜,所以你只要了解这个逻辑,这俩任务队列相比之下肯定用 LinkedBlockingQueue。
ArrayBlockingQueue 的存在更像是用来突出 LinkedBlockingQueue 更好用。
(ArrayBlockingQueue:我没惹你们任何人!