当前位置: 首页 > 科技观察

Java线程池没用,直接崩溃了,,

时间:2023-03-12 11:16:01 科技观察

背景介绍大家好,今天给大家讲一个比较硬核的技术知识,就是Java线程池在生产中的高并发优化项目。可能很多兄弟都听说过Java线程池的理论原理,也知道它是如何工作的,但是却从来没有在项目中玩过Java线程池,更谈不上Java线程池在高并发环境下的优化了。那么今天就来讨论下这个Java线程池在生产项目中的高并发优化吧!线程池的基本工作原理既然要说线程池,至少大家应该对Java线程池的基本工作原理有所了解。要把线程池的原理解释清楚,甚至分析JDK线程池的源码层面,可能需要单独写一篇文章。这不是我们这次的主题,所以先告诉大家线程池最简单的原理。线程池,简单来说就是有一个池子,里面有一堆线程。一般这些线程不会被销毁,它们会一直存在,然后就可以不断的向线程池提交任务。线程池会取出线程来执行你的任务。任务执行完后,线程不会终止,继续在线程池中待命。我们看下图1:图1但是这时候会有一个关键的问题,那就是线程池中的线程数通常是有限制的。注意,这里说的是通常情况,因为Java线程池的真正原理,其实通过定制的手段,Java线程池可以有各种各样的表现。我们这里说的是最基本的情况。即线程池中的线程数量是固定的、有限的。那么如果你一次性向线程池提交了太多的任务,而此时所有的线程都在忙着运行自己的任务,如果这个时候你要提交新的任务,你认为会发生什么情况呢?可以提交吗?看下图2:图2你当然不能提交,但是这个时候线程池就只能拒绝你了吗?这不是真的。为了应对这种情况,线程池通常会设置一个队列让你提交任务,让你的任务在队列中等待一段时间,等待某个线程运行完自己的任务变得空闲,然后运行此队列中的任务。注意,这也是通常的情况,因为Java线程池其实可以通过定制有其他的表现,但是通常我们会这样设置线程池。如下图3所示:图3线程池高并发场景下的问题分析完成。那么下一个问题来了。以上就是Java线程池最基本的原理和用法,但是真正投入生产项目之后,他会遇到什么样的问题呢?首先,最大的问题是提交到线程池的任务可能都需要执行各种网络IO任务。比如RPC调用其他服务,或者在后台处理DB中的大量数据,那么一个线程运行完一个任务可能需要很长时间,少则几百毫秒,多则几秒,甚至几十秒。有这样的可能。如下图4所示:图4中的第二个问题,大家注意了,上图中没有这个东西,就是有的任务是RPC调用,可能只需要几百ms,有的任务比较大大量的数据操作,可能需要几十秒。所以,其实在一个普通的线程池中,各种任务都在运行,这就导致线程池中的一个线程什么时候能完成一个任务是不确定的,因为这个任务可能是一个RPC调用,也可能是一个大数据量加工。第三个问题,一个Http请求中可能会有一些任务。本来,在一个Http请求的处理过程中,会依次处理多个耗时的任务。现在为了优化性能,需要将多个任务提交到线程池中,使用多个线程并发执行多个任务来提高本次请求的性能。这个Http请求需要等待这多个并发运行的任务执行完毕才会返回响应给用户。如下图5所示:图5所以,最终的大问题是生产项目中运行的这种线程池是提供给各种任务共享的,比如定时RPC调用,定时大数据量处理,前台Http请求同时处理多项任务。因此,在生产环境繁忙期间,可能会出现以下场景:线程池当前正在运行多个定时RPC调用和定时的大规模数据处理任务。这些任务特别耗时,导致很多线程忙碌。一些线程空闲。那么这个时候系统给C端用户提供的接口就有了高并发访问的场景。大量的Http请求过来,每个请求都要提交多个任务到线程池中并发运行,导致线程池中少数空闲线程快速运行。满了,然后大量的任务进入线程池的队列,开始排队等待。如下图6所示:图6此时必然会导致大量Http请求挂死,因为很多Http请求任务在线程池中排队,无法运行,Http请求无法返回响应,给用户点击app/网页前端,来回点击没有任何反应,系统卡死的感觉!如下图7所示:图7线程池高并发场景下的性能优化针对的是这个生产环境问题,我们首先要做的最大的改进就是把各个任务从一个线程池中分离出来,让它们互不影响.也就是说定时RPC任务放在一个线程池,定时DB海量数据处理任务放在另一个线程池,Http请求多任务并发处理放在独立的线程池,每个人使用自己的线程pool和resources互不影响。如下图8所示:如果图8如上图所示,我们有一个专门处理Http请求的线程池,压力立马减轻,因为Http请求的任务通常需要几十ms到100ms水平,整体速度非常快,没有线程池中定时RPC、定时DB访问等耗时任务来捣乱。因此,Http请求的专用线程池可以轻松+愉快、快速地处理所有的Http请求任务。即使在高并发场景下,也可以通过线程池增加线程资源,合理抵御高并发压力。另一种是在线上系统的生产环境运行线程池任务。我们通常会在公司或者项目内部制定一个统一的线程池监控框架。所有的线程池任务都需要封装到线程池监控框架提供的一个类中,然后使用这个类实现任务排队和运行时间的二维监控数据统计。如下代码所示://线程任务包装类,采用装饰设计模式publicclassRunnableWrapperimplementsRunnable{//实际要执行的线程任务privateRunnabletask;//线程任务创建时间privatelongcreateTime;//线程池运行的线程任务的开始时间privatelongstartTime;//线程池运行的线程任务结束时间privatelongendTime;//当这个任务创建的时候,它的创建时间会被设置//但是之后有可能这个任务提交到线程池之后,就会进入线程池队列publicRunnableWrapper(Runnabletask){this.task=任务;this.createTime=newDate().getTime();}//当任务在线程池中排队时,这个run方法不会被运行//但是当任务完成排队并有机会在线程池中运行时,这个方法就会被调用//此时,可以设置线程任务开始运行时间publicvoidrun(){this.startTime=newDate().getTime();//这里可以调用监控系统的API上报监控指标//使用线程任务的startTime-createTime,其实就是一个任务队列时间//monitor.report("threadName","queueWaitTime",开始时间-创建时间);//然后就可以调用被包装的实际任务的run方法task.run()了;//任务运行结束后,会设置任务运行结束时间this.endTIme=newDate().getTime();//这里可以调用监控系统的API,实现监控指标的上报//使用线程任务endTime-startTime其实就是任务运行时间//monitor.report("threadName","taskRunTime",endTime-startTime);}}通过上面的代码可以清楚的看到,只要我们所有提交给线程池的任务,都是用RunnableWrapper类的一个框架封装起来的,是基于装饰模式进行封装的。此时就可以得到线程任务的创建时间、开始时间、结束时间,进而可以计算出任务的排队时间和运行时间,通过监控系统上报。这时,通过在监控系统中配置告警条件,我们就可以实现不同线程池中各个任务耗时指标的上报。阈值,将发出自动警报。如下图9所示:图9是一个总结。今天的文章到此结束,给大家讲讲我们线程池在生产项目中的生产问题,高并发如何优化,以及生产环境中的监控方案。.希望大家学以致用,以后在项目中使用线程池的时候,可以灵活运用我们文章中学到的知识点。