线程执行超时导致线程池堵塞
问题
在写爬虫下载文件的时候,因为部分线程下载速度过慢,导致最后所有的核心线程占满,从而无法继续处理任务。
扩大新线程
这个方法肯定是不可行的,理论上而言,不管有多少核心线程,都可能发生同时下载时间过长。
Future 的 cancel() 方法
百度的方法来说,就是使用 Future 进行判断,如果超时,返回的结果肯定不是理想结果,这个时候调用 future.cancel()
方法,来对线程进行取消。
但是其中最主要的问题就是,future.cancel()
方法只是对线程修改了中断标志位,线程的结束与否,仍然取决于线程自己的代码逻辑。而在下载的时候,此时线程本身就是在正常运行,且不断读取数据流,这种时候是无法运行到判断中断标志位那一步的。
解除绑定
此时我想到的是超时解除绑定关系,超时时,让线程池不再拥有超时线程,并重新创建核心线程。
奈何 jdk8 的 API 并没有提供相应接口,最新版本也没有去查了
一点点小技巧
基于解除绑定的想法,我想起了一条建议,多线程多队列
即将队列拆分,分给多个线程池进行执行,同时使用 CountDownLatch 监听,当所有任务结束或任务超时时,关闭线程池,即 shutdown()
。
线程池的 shutdown()
方式是停止添加队列,等待所有任务结束,关闭线程池。
基于这个方法,我尝试了
- 将队列分割
- 将分割后队列提交到新 new 的 线程池中
- CountDownLatch 监听,60 秒后任务没有执行完毕的话,则调用线程池的
shutdown()
方法,线程池等待关闭线程池 - 主线程重复第 2 步操作
以上方法的好处就是在多个线程卡顿时不影响其他任务的执行过程,而相比无限调大核心线程数,又不会一直让 cpu 高负荷运转
当然这有很多缺点
- 增加了很多内存开销,极端情况下会 OOM
- 极端情况下也会出现 cpu 满负荷
当然这也是极端情况下,而且我打算创建一个固定数量的线程池集合,用于控制线程池数量,当其中某个线程池为 null 时,再进行新建。
不过我还没有进行优化,不确定是否能够实现。但目前而言,已经完美解决了我的问题。