作为一名互联网程序员,经常需要面对高并发的场景,为了更好地提高系统的吞吐量和响应速度,我们通常采用并发编程。而线程池技术也是Java并发编程中的一个重要组成部分。本文将分享我的Java线程池使用经历,以及Java线程池在转转平台的实践。
10多年的枣阳网站建设经验,针对设计、前端、开发、售后、文案、推广等六对一服务,响应快,48小时及时工作处理。成都全网营销的优势是能够根据用户设备显示端的尺寸不同,自动调整枣阳建站的显示方式,使网站能够适用不同显示终端,在浏览器中调整网站的宽度,无论在任何一种浏览器上浏览网站,都能展现优雅布局与设计,从而大程度地提升浏览体验。成都创新互联从事“枣阳网站设计”,“枣阳网站推广”以来,每个客户项目都认真落实执行。
线程池是一种常见的多线程并发编程技术,它将多个线程组织在一起,以便能够更有效地管理和控制它们的执行。线程池中的每个线程都可以被重复利用,避免了频繁地创建和销毁线程所带来的开销,同时还可以限制系统中的线程数量,从而避免了资源的浪费和竞争。2019年刚参加工作时,我第一次使用线程池是在处理用户请求,该请求需要聚合多个服务的数据,然后返回给用户。调用的服务均比较耗时,如果串行的去调用那么系统的响应时间就会非常长。所以,我决定使用多线程来并行执行这个聚合操作,因此也引入了线程池。在Java中线程池是通过java.util.concurrent包提供的ThreadPoolExecutor类来实现的。通过创建ThreadPoolExecutor对象并设置其参数,线程池运行大致分为4个阶段大致如下图:
对于刚接触Java线程池的同学,遇到的第一个问题就是如何合理地设置线程池参数,以最大限度地发挥线程池的性能,避免线程池满载或资源浪费的问题。通过互联网我们能收集到各类设置线程池参数的建议:
总之,合理地设置线程池的参数需要程序员对线程池运行原理有足够的了解,并且有对应用程序的负载调优和硬件资源调优的经验,显然这是非常困难得。因此我最终选择中庸的配置方法,根据IO密集型来设置线程数为CPUs*2,根据平均任务时长与QPS来预估队列长度为1000,设置完毕上线且能够正常运行,就这样我与线程池的相遇如此简单的结束了。
转眼间来到2021年,随着业务发展App使用的人数越来越多,对服务性能的要求也越来越高。因此我们在618前对服务进行全链路压测,在压测中线程池出现以下问题:
对这些问题进行复盘可以发现在实际应用中,即使是微服务架构的同一个模块中由于业务的复杂性也需要引入多个线程池来进行业务隔离,而不同的业务场景也需要对线程池参数进行不同的设置。比如用户请求场景需要更大的核心线程数来进行快速响应,数据导出场景需要更大的队列来缓解大量的导出任务,突发流量场景需要更大的最大线程数和任务队列等等。而为了找到合适各场景的参数值,我们需要重复进行压测、调整参数、上线的过程,消耗大量的人力物力。最终我们将遇到的问题归纳为两方面:
为解决这些问题我们设计并实现一套可动态调整可监控的线程池,具体设计与实现如下。
动态线程池主要包含客户端、监控平台、配置后台三部分:
动态参数调整主要依赖ThreadPoolExecutor提供的如下的set方法:
public void setCorePoolSize(int corePoolSize);
public void setMaximumPoolSize(int maximumPoolSize);
public void setKeepAliveTime(long time, TimeUnit unit);
public void setThreadFactory(ThreadFactory threadFactory);
public void setRejectedExecutionHandler(RejectedExecutionHandler handler);
综合考虑需求和风险我们最终选择使用set方法实现对corePoolSize,maximumPoolSize的动态调整,setCorePoolSize和setMaximumPoolSize方法能够直接对当前线程池进行赋值,并且能够自动调整线程数。若当前值大于修改值,通过标记中断的方式回收多余线程。若当前值小于修改值,setMaximumPoolSize值进行赋值不操作线程,setCorePoolSize会取排队的任务数和修改差值的最小值,来新增对应数量的核心线程数。可以看出set方法能够平稳的进行参数的修改。这样解决了线程数的动态调整问题,但ThreadPoolExecutor不提供对工作队列的动态调整。重新回顾诉求我们只是想要能够调整工作队列的大小而不是替换线程池的工作队列,因此我们基于LinkedBlockingQueue实现长度可调的工作队列。最终实现效果如下图:
同样的线程池监控也依赖于ThreadPoolExecutor提供的如下的get方法:
public int getActiveCount();
public BlockingQueuegetQueue;
public int getCorePoolSize();
public int getMaximumPoolSize();
public long getTaskCount();
通过这些get方法可以实时的获取到线程池的运行数据,将这些数据上报监控与报警平台便可让程序员实时查看具体数据。具体的实现方式可以分为两种:
对线程池的监控主要是对工作线程和工作队列进行监控,因此我们整理如下监控指标:
指标 |
方案 |
作用 |
线程池活跃度 |
activeCount /maximumPoolSize |
用于描述线程池负载情况 |
队列饱和度 |
queueSize / queueCapacity |
用户描述工作队列负载情况 |
任务阻塞阻塞时间 |
executeStartTime-inQueueTime |
用户描述任务排队情况 |
最终监控报警效果:
动态线程池自在转转平台应用以来,我们通过日常监控及时发现潜在问题,通过自动容灾应对突发流量,通过压测调优提升线程池性能,为转转平台服务在多年的618、双十一活动中保驾护航,未出现一次因线程池导致的线上事故。希望本文能够帮助到遇到同样问题的同学们。
武翱,转转-平台技术部-后端开发。
当前标题:动态线程池在转转平台的实践
文章分享:http://www.gawzjz.com/qtweb2/news40/1540.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联