jdk线程池大概设计

247 阅读3分钟

这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

秋风萧瑟洪波涌起,先想后看,是学习新技术的最快的方式。

想我如何设计线程池

功能

写一段代码,要先搞明白其需求是什么。 按照jdk线程池,我列举了一个最简单线程池具备的功能。

初始化

execute 接口

  1. 判断核心线程数<coreSize(不论线程是否处于idle)? 新建core线程执行
  2. 判断队列是否还能添加元素 ? 添加进队列
  3. 判断当前线程数量<maxSize ?创建线程
  4. 执行拒绝策略
  5. 线程池优雅关闭

自己写了一下,主要是一些数据结构以及核心的execute接口,如屎一般。本文来讲一下自己实现线程池遇到的一些问题以及结合源码找到的一些解决思路。

数据结构

先来对比一下线程池基本的数据结构。

image.png

image.png

没错,上面那坨就是我的code,对比之下,下面ctl一个变量直接就表达了我的两个变量。ctl这个变量在我来看并不是一定要合在一起。

再看看worker定义: 因为有两类work,并且非core的执行线程需要支持在非idle情况下也能执行task,所以我就去分开来。 image.png

最后的核心execute接口就不多说了,无脑家synchorized是我的常态。哪天我能写出cas代替所有的锁,说明我已经进化了。

image.png

问题

1. 如何做核心线程+1以及核心worker创建执行的线程安全问题

找到以下代码: java.util.concurrent.ThreadPoolExecutor#addWorker代码两部分,前部分用CAS+1,后半部分new Worker执行task。

image.png

循环+CAS的成功并不代码后面的代码就不需要同步了,简单操作用CAS,复杂操作还是的lock。

image.png

2. 核心线程主要是用来消费队列的任务,那么core worker先执行task在消费队列,还是直接消费队列?

这属于实现上的一个细节问题,如果不去真的实现很难看到这个东西。两者实现相比,复杂度也是直线上升。

image.png

3. pool线程不依赖blockQueue实现阻塞,应该如何做notify机制来确保不会一直占用CPU资源?

我用了大量时间来做这个notify机制,

image.png

但实际上我记错了,pool worker也是直接消费队列task,而不是自己新建。

4. pool worker如何实现idle超时释放

这种超时释放的功能,就两种实现的套路,第一种是定时任务扫描,第二种就是每次请求某个方法的时候进行更新。为了性能,一般都是选择后者。 代码比较复杂,感觉是是通过BlockQueue的poll阻塞到idle的超时时间,如果超时则表明需要杀死这个线程,

image.png

线程池监控

之前遇到过个线上问题,接口调用线程池执行一个http请求,发送消息的, 调用方调用可接口,但是消息一直没有发。查日志也是没有调发消息的请求。线程池的参数设置的Integer.MAX_VALUE。后来猜测是线程池堆积的任务太多,还没有执行到发消息的任务。

对于生产使用的线程池,感觉有必要考虑一下几点:

  1. 这个业务是否需要独立一个线程池。
  2. 线程池需要打任务的数量,消费的数量,这个有助于设置poolSize参数
  3. 用有界队列,配合拒绝策略,可以在拒绝策略设置调用execute的线程执行任务