本文转载自射手微信公众号“射手茶杯”。转载请联系射手茶杯公众号。本文主要介绍线程池的一些进阶玩法。面包超人小镇建筑1.线程池简单简单4连发1.如何设置线程池的核心线程数?2、8C16G机器能承受3W多少qps?3、如何动态修改线程池参数?4、线程池能否先启动最大数量的线程,再将任务放入阻塞队列?下例机器配置统一为8核16G!2、如何设置线程池的核心线程数?首先是不正确的答案:IO密集型设置为2n,计算密集型设置为n+1。为什么错了?因为核心线程数需要具体分析,使用线程池的业务场景不同,解决方案自然也不同,我举个例子做个详细的分析,然后总结出一个可以适用于不同场景的方法论!!!例如:1.假设要给100w个用户发放优惠券,通过线程池异步发送2.假设一个线程池执行发放优惠券的任务一共耗时50ms,其中45ms在io,以及5ms在计算中(真正的io耗时计算时间可以通过记录日志判断时间差取平均值)3.如何设置线程池的参数才能快速发放这100w张优惠券?先抛出答案公式,然后证明这个公式的正确性:核心线程数=CPU核心数*((Io耗时/计算耗时)+1)核心线程数=8C*((45ms/5ms)+1)=8045ms/5ms是什么意思?CPU在等待IO返回时,可以取出CPU时间片做其他计算,45ms可以多处理9另外还有一个5ms的计算任务,也就是说:当一个CPUcore执行这个50ms的发券任务,可以并发启动10个线程来处理这个任务!8CCPU最多可以同时拥有8个线程。8核并行处理任务,8*10=80个线程每秒可处理1000ms/50ms=20个任务可计算出线程池执行任务的峰值qps=20*80=1600发放100w张优惠券所需时间:100w/1600=625S,也就是说10分钟左右可以发100w券。错误结论:在处理这个任务的情况下,可以将核心线程数设置为80,将机器的CPU性能压榨到极限。什么?为什么80的计算不正确?因为核心线程数设置为80,几乎吃光了所有的CPU时间片,CPU负载会达到100%;想象一个生产环境,如果你的机器CPU负载是100%,panic还是panic?(CPU负载满时机器不会关机,但是没有CPU资源处理用户请求,表现为服务假死/机器请求半天不响应)设置核心线程数在线程池要考虑CPU使用因素1.每台机器的操作系统需要消耗一定的CPU资源;假设使用了2%的CPU资源;2、如果是面向用户的服务,处理用户请求也会消耗CPU资源。可以通过一些监控系统,看看繁忙时间段内CPU的负载情况如何?假设使用了10%的资源;3.如果除了发券任务的线程池外还有其他线程池在运行,还需要统计其他线程池消耗的CPU资源,假设使用了13%的资源;4、现实中有些中间件框架也会使用线程池,会消耗一些CPU资源,这里暂且不考虑。在我的实际项目中,有一个专门负责运行定时任务和消费MQ消息的服务:我需要考虑的几点:1.操作系统的CPU资源会占用2%的CPU资源。2、MQ消费消息会占用5%的CPU资源3、还有其他定时任务使用线程池运行任务,占用13%的CPU资源。4、机器CPU在无人监控的非必要时段不能超过60%。60%-2%-5%-13%=40%发放100w张优惠券的线程池只能消耗40%的资源,所以最大核心线程数可以设置为:核心线程:80*40%=321;当CPU为100%时,可以设置80个线程运行任务,当CPU为40%时,可以设置32个线程运行任务。这样,系统正常运行大约是CPU的60%,即使偶尔上升到70%-80%。不要惊慌~补充:为什么使用线程池时没有考虑上下文切换?1ms=1000us,一次上下文切换大约1us,上下文切换的时间与执行任务的时间相比可以忽略不计。结论:CPU核数*((Io耗时/计算耗时)+1)这是机器CPU负载为100%时的极限值,乘以预期CPU负载百分比,计算出最优数实际情况下的线程数;PS:核心线程数设置错误,不想改代码重新发布,可以继续看第三个问题,如何动态修改线程池参数!2、8C16G机器能承受3W多少qps?先计算单机3w的QPS可以除以单机的qps来计算需要的机器数。想要知道单机某个接口的QPS很简单,做个压测就可以了。但是很显然,如果面试的时候被问到这个问题,你是不能压力测试的。其实面试官是在考察你对线程池的理解,再往下看~假设一个用户优惠券系统的qps在3w左右。大多数服务通常都部署在Tomcat上,而Tomcat内部也是通过线程来处理用户请求的,Tomcat也是通过线程池来管理线程的。其实计算Tomcat的实际并发数和理想状态下能支持的并发数就够了。根据上一题分析,优惠券发放界面耗时50ms,8CCPU占用100%。抛开内存、磁盘、网络等其他开销,线程池的QPS限制为1600,没有考虑是否有其他线程池和随机性。消耗CPU资源的东西。假设CPU只能维持70%左右的负载;单台机器的qps只能有1600*70%=1120,即使11003w/1100=27.27四舍五入也需要28台机器左右。作为一个有经验的开发者,在实际部署的时候,多扩展几台服务器来覆盖情况是绝对必要的。建议分两个集群部署32-36台机器。3、如何动态修改线程池参数?为什么需要动态修改线程池参数?比如第一次发券任务需要10分钟发10万张。不想用其他方式解决优惠券任务,机器CPU负载很低,只有1%;(苦心修改线程池参数,强行举例)看到第一题和第二题,我想你也会有收获提供以下信息:100w张优惠券使用8C16G机器发放,处理每张优惠券需要50ms任务,其中45ms在IO,5ms在计算,核心线程数设置为32,CPU负载达到40%左右,10分钟可以发优惠券。如果要发200万张优惠券,最快的方法是将核心线程数设置为32到64,CPU负载在80%左右。如何动态修改线程池参数?JDK的ThreadPoolExecutor提供了修改线程池参数的API//修改拒绝策略ThreadPoolExecutor.setThreadFactory//修改线程工厂(不能直接修改阻塞队列的大小,如果想达到修改阻塞的效果队列,只是对线程池做一些封装)1.首先,将线程池定义为一个Bean对象;@Bean("refreshLowPriceExecutor")publicThreadPoolExecutorrefreshLowPriceExecutor(){finalBlockingQueue
