文章插图
我们使用 wrk 工具对这个接口进行一个简单的压测,可以看到 TPS 为 75,性能的确非常差 。
文章插图
细想一下,问题其实没有这么简单 。因为原来执行 IO 任务的线程池使用的是 CallerRunsPolicy 策略,所以直接使用这个线程池进行异步计算的话,当线程池饱和的时候,计算任务会在执行 Web 请求的 Tomcat 线程执行,这时就会进一步影响到其他同步处理的线程,甚至造成整个应用程序崩溃 。
解决方案很简单,使用独立的线程池来做这样的“计算任务”即可 。计算任务打了双引号,是因为我们的模拟代码执行的是休眠操作,并不属于 CPU 绑定的操作,更类似 IO 绑定的操作,如果线程池线程数设置太小会限制吞吐能力:
文章插图
使用单独的线程池改造代码后再来测试一下性能,TPS 提高到了 1727:
文章插图
可以看到,盲目复用线程池混用线程的问题在于,别人定义的线程池属性不一定适合你的任务,而且混用会相互干扰 。这就好比,我们往往会用虚拟化技术来实现资源的隔离,而不是让所有应用程序都直接使用物理机 。
就线程池混用问题,我想再和你补充一个坑:Java 8 的 parallel stream 功能,可以让我们很方便地并行处理集合中的元素,其背后是共享同一个 ForkJoinPool,默认并行度是 CPU 核数 -1 。对于 CPU 绑定的任务来说,使用这样的配置比较合适,但如果集合操作涉及同步 IO 操作的话(比如数据库操作、外部服务调用等),建议自定义一个 ForkJoinPool(或普通线程池) 。
重点回顾线程池管理着线程,线程又属于宝贵的资源,有许多应用程序的性能问题都来自线程池的配置和使用不当 。在今天的学习中,我通过三个和线程池相关的生产事故,和你分享了使用线程池的几个最佳实践 。
第一,Executors 类提供的一些快捷声明线程池的方法虽然简单,但隐藏了线程池的参数细节 。因此,使用线程池时,我们一定要根据场景和需求配置合理的线程数、任务队列、拒绝策略、线程回收策略,并对线程进行明确的命名方便排查问题 。
第二,既然使用了线程池就需要确保线程池是在复用的,每次 new 一个线程池出来可能比不用线程池还糟糕 。如果你没有直接声明线程池而是使用其他同学提供的类库来获得一个线程池,请务必查看源码,以确认线程池的实例化方式和配置是符合预期的 。
第三,复用线程池不代表应用程序始终使用同一个线程池,我们应该根据任务的性质来选用不同的线程池 。特别注意 IO 绑定的任务和 CPU 绑定的任务对于线程池属性的偏好,如果希望减少任务间的相互干扰,考虑按需使用隔离的线程池 。
最后,我想强调的是,线程池作为应用程序内部的核心组件往往缺乏监控(如果你使用类似 RabbitMQ 这样的 MQ 中间件,运维同学一般会帮我们做好中间件监控),往往到程序崩溃后才发现线程池的问题,很被动 。
推荐阅读
- 淘宝促销宝是干什么的 淘宝促销宝怎么使用
- 车内a/c是什么意思?意思很简单,使用技巧也很简单
- 淘宝选词工具 选词助手免费使用的吗
- 店铺宝满就送优惠券怎么使用 淘宝如何设置赠品
- 使用环保袋的好处
- 手机千牛上的单品宝在哪里? 手机千牛可以使用单品宝吗
- 淘宝店极速推有用吗 淘宝极速推怎么使用
- 淘宝免费选词工具 选词助手免费使用的吗
- 淘宝店侦探手机版下载 店侦探怎么使用
- 超级推荐新品推广怎么使用