前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java线程池配置由繁至简,找到适合自己的天命线程池(一)

Java线程池配置由繁至简,找到适合自己的天命线程池(一)

原创
作者头像
苏易困
发布2022-08-22 20:42:16
2180
发布2022-08-22 20:42:16
举报

前提知识🧀

还记得刚入这行,还处于实习阶段的我,第一个项目就震撼到我了,因为发现自己熬夜苦读学习的知识和实际工作中需要的差别太大了,再加上项目用到的一些框架模块都很久,我连阅读代码的业务逻辑都很困难;其中让我印象深刻的就有一个封装了群发http请求的工具类,里面就用到了线程池,眼花缭乱的参数让那时的我头痛不已,有的参数甚至不知道是做什么用,为什么要设置成这个?

时间是让人猝不及防的东西,这么久终画上句。

免不了认识的7个基本参数

代码语言:javascript
复制
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {}

贴上源码里的注释 并附加一些个人向的补充(参数加星代表比较重要):

*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一样,你可以在一开始就设置你想要的容量。

代码语言:javascript
复制
//无参构造
public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);
}

//预设最大容量的构造
public LinkedBlockingQueue(int capacity) {
    if (capacity <= 0) throw new IllegalArgumentException();
    this.capacity = capacity;
    last = head = new Node<E>(null);
}

这样横向一比较,LinkedBlockingQueue的吞吐量比ArrayBlockingQueue要高,可以跟ArrayBlockingQueue一样规定最大容量,也可以无界;这么一比,LinkedBlockingQueue完胜,所以你只要了解这个逻辑,这俩任务队列相比之下肯定用LinkedBlockingQueue。

ArrayBlockingQueue的存在更像是用来突出LinkedBlockingQueue更好用。

  • SynchronousQueue

除去上面两个队列外,还有这个比较特殊的队列,因为它没有容量,或者说容量为0,它的每一个put 操作必须等待一个take 操作,也就是它的上限在于take 操作的效率,也就是工作线程的效率。这个队列在当有足够多的消费者时,是最合适的队列。

换句话说,假如你需要线程池去处理的任务数不多,qps不高,甚至峰值也不高,未来也不会有大的变化,那恭喜你,你已经找到了你的真命线程池,直接使用Executors.newCachedThreadPool(),它完全能胜任你的需求,甚至对原因不太在意的同学,可以马上关掉页面用起来了。

罢特,我也相信,这种情况还是少,大部分人都是因为需要线程池来“兜底”,也就是任务数或者任务峰值线程池真的撑不住,才来查询怎么找到适合自己的配置,那咱们不慌,就继续往下看。

阿里巴巴Java开发手册为什么不推荐使用Executors类自动生成的几个线程池

上面提到了Executors.newCachedThreadPool(),Executors相当于对线程池的一个工具类,系统提供了几个参数已经预设好,一行代码就可以创建的线程池供开发者使用,但是《阿里巴巴Java开发手册》里却不推荐使用,这是为什么呢?

我们先来看一下上面提到的,通过Executors类创建的线程池newCachedThreadPool:

代码语言:javascript
复制
//创建一个系统预设好的线程池
ExecutorService executorService = Executors.newCachedThreadPool();

//构造函数如下
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, 
                                  Integer.MAX_VALUE,
                                  60L, 
                                  TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

我们可以看到:核心线程为0,最大线程数为MAX(可以理解无上限),任务队列使用的是SynchronousQueue,乍一看好像没什么问题,那为啥手册里不推荐使用呢?

手册上是这么说的,我们直接看看2):

什么意思呢?就是说这个预设好参数的线程池CachedThreadPool,它的最大线程数是Integer.MAX_VALUE,我们可以理解为最大线程数无上限,当生产者提交任务的量攀升,消费者处理不过来,就会不停地添加工作线程,因为线程数没有上限,会不停地添加线程,直到发生OOM。

看到这里我相信你已经知道为啥上面推荐使用CachedThreadPool时要加那么多的前置条件了。

因为一旦消费者处理不过来,就有引起OOM的风险存在,谁又敢乱用呢。

而且在知道这个后我们可以举一反三,还有什么会因为任务数变多而骤增,进而也会发生OOM呢,没错,就是任务队列数。

所以只要以下条件满足一个,在任务处理不过来的情况下就有可能发生OOM:

  • maximumPoolSize为Integer.MAX_VALUE(或很大
  • workQueue为无界队列(或很大

然而Executors这个工具类预设的几个线程池,不是最大线程数是Max,就是任务队列是无界的,都满足上面的条件,所以系统预设的线程池,手册都不建议使用

手册的意思很明了了,都不建议使用的意思其实就是:

要根据项目,自己来设置合适的参数。

写在最后的最后

因为篇幅问题,我们这篇文章只是开个头,讲述一些基本参数,引出最后的问题。因为我觉得一篇文章的字数大概在2000~3000字比较合适,内容太多的话,可能不太好接受,不太好吸收;不过放心其实后面一篇也已经完成,只剩一点修修补补的工作,马上就会发上来。

但也许有人会觉得比较啰嗦,但也没差啦,我也是跟着自己的feel来的,比如每个标题及其内容,我也是反复阅读过好几次才排好顺序,循序而进,可能不是把基础全铺上再来给结论,而是引导一个又一个的问题来讲述,如果你能有所收获,那么我会很开心的~如果可以点赞评论收藏分享,那么我的动力会更足的,谢谢大家~

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前提知识🧀
  • 免不了认识的7个基本参数
  • 几个重要参数的要求和相互之间的逻辑关系
  • 常用的几个任务队列
  • 阿里巴巴Java开发手册为什么不推荐使用Executors类自动生成的几个线程池
  • 写在最后的最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档