Java线程池介绍及使用(这一篇就够了) |
您所在的位置:网站首页 › java线程池配置 › Java线程池介绍及使用(这一篇就够了) |
点击 Mr.绵羊的知识星球 解锁更多优质文章。 目录 一、介绍 1. 简介 2. 分类(按线程池执行任务分类) 3. 架构设计 4. 优点 二、使用场景 1. 快速响应用户请求,响应速度优先 2. 单位时间处理更多请求,吞吐量优先 三、线程池参数 1. 七大核心参数 2. 参数如何配置 四、执行流程 1. 线程池执行流程图 2. 文字+图片描述 五、实际应用 1. 案例一 一、介绍 1. 简介一种线程使用模式。 线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。 2. 分类(按线程池执行任务分类)(1) cpu 密集型任务 cpu 密集型任务,需要线程长时间进行的复杂的运算,这种类型的任务需要少创建线程,过多的线程将会频繁引起上文切换,降低任务处理速度。 (2) io 密集型任务 io 密集型任务,由于线程并不是一直在运行,可能大部分时间在等待 IO 读取/写入数据,增加线程数量可以提高并发度,尽可能多处理任务。 3. 架构设计(1) Executor ThreadPoolExecutor实现的顶层接口是Executor,顶层接口Executor提供了一种思想:将任务提交和任务执行进行解耦。用户无需关注如何创建线程,如何调度线程来执行任务,用户只需提供Runnable对象,将任务的运行逻辑提交到执行器(Executor)中,由Executor框架完成线程的调配和任务的执行部分。 (2) ExecutorService接口增加了一些能力 a. 扩充执行任务的能力,补充可以为一个或一批异步任务生成Future的方法。 b. 提供了管控线程池的方法,比如停止线程池的运行。 (3) AbstractExecutorService AbstractExecutorService则是上层的抽象类,将执行任务的流程串联了起来,保证下层的实现只需关注一个执行任务的方法即可。 (4) ThreadPoolExecutor ThreadPoolExecutor实现最复杂的运行部分,ThreadPoolExecutor将会一方面维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并行任务。 4. 优点(1) 降低资源消耗:通过重复利用已创建的线程,降低线程创建和销毁造成的开支。 (2) 提高响应速度:当任务到达时,任务可以不需要等待创建线程,直接使用线程池中创建好的线程。 (3) 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅消耗系统资源还会降低稳定性。 (4) 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。 注意:线程池虽然好用,但是使用时一定要知道其线程池参数如何配置和使用,执行流程怎样的,详情见下文。 二、使用场景 1. 快速响应用户请求,响应速度优先(1) 比如一个用户请求,需要通过 RPC 调用好几个服务去获取数据然后聚合返回,此场景就可以用线程池并行调用,响应时间取决于响应最慢的那个 RPC 接口的耗时。 (2) 某些批量操作,用户请求批量删除10个账号,希望能够快速得到响应,如果删除一个账号需要1秒,删除10个就需要10秒。如果你用了线程池异步执行,显然会很快得到响应结果。具体多快,取决于你服务器的性能和线程池的参数配置。 (3) 或者一个注册请求,注册完之后要发送短信、邮件通知,为了快速返回给用户,可以将该通知操作丢到线程池里异步去执行,然后直接返回客户端成功,提高用户体验。 这样的场景就建议不设置队列或设置短的队列去缓冲并发任务,调高corePoolSize和maxPoolSize去尽可能创造多的线程快速执行任务。 2. 单位时间处理更多请求,吞吐量优先比如接受 MQ 消息,然后去调用第三方接口查询数据,此场景并不追求快速响应,主要利用有限的资源在单位时间内尽可能多的处理任务,可以利用队列进行任务的缓冲 这类场景任务量巨大,并不需要瞬时的完成,而是关注如何使用有限的资源,尽可能在单位时间内处理更多的任务。所以应该设置队列去缓冲并发任务,调整合适的corePoolSize去设置处理任务的线程数。在这里,设置的线程数过多可能还会引发线程上下文切换频繁的问题,也会降低处理任务的速度,降低吞吐量。 三、线程池参数 1. 七大核心参数(1) corePoolSize:线程池中常驻的核心线程数。 (2) maximumPoolSize:线程池能容纳的同时执行的最大线程数。 (3) keepAliveeTim:多余的(除核心线程外的其他线程)空闲线程的存活时间。 (4) unit:keepAliveTime的单位(ms、s...) (5) workQueue:任务队列,已提交但是未执行的任务。 (6) threadFactory:生成线程池中线程的工厂。 (7) handler:拒绝策略,当前队列满了并且正在工作的线程等于最大线程数(maximumPoolSize)时如何处理任务。 2. 参数如何配置网上有很多计算公式,例如:CPU+1、CPU核数*2。或者根据cpu密集型或io密集型,进行配置。但是这样真的合理吗?可以适用所有场景吗?答案肯定是不行的。那该如何配置呢? (1) 根据经验和实践配制出合理的参数。 (2) 根据监控服务线程池资源利用情况结合业务场景动态配制合理参数。 有的兄弟可能觉得说了等于没说,我觉得你应该先知道线程池执行流程(四、执行流程)和各参数使用情况,需要结合服务器配置以及业务场景,动态调整线程池参数。你有什么比较好的线程池参数设置方式呢?欢迎评论留言! 四、执行流程 1. 线程池执行流程图2. 文字+图片描述 咱也可以按照生活中的场景,讲一下这个线程池参数的使用和执行流程,方便兄弟们理解。 某某公司分部(threadFactory)为客户办理业务,一共有4个柜员(maximumPoolSize),不忙的时段就分配2个柜员(corePoolSize)办理业务,剩余2人休息。摆了4个椅子(workQueue),供客户进行等待。并且这个公司给员工定了个规定,就是当工作区的柜员都在工作,并且等待区的座位都做满时,那么处于休息区的柜员要出来帮忙。直到所有柜员都在工作,等待区也坐满了。那为了我们公司员工身体的考虑,暂时拒绝任何人来办理业务(handler),如果有某个员工空闲下来并且超过了10(keepAliveTime)分钟(unit),那就可以回到休息区休息,但是必须保证有2个柜员在工作区。 (1) 两个柜员为两位顾客办理业务 (2) 给客户1,2办理过程中,陆续来客户把坐位坐满了。这时客户7来了~ (3) 柜员3出来帮忙,至于柜员3给新来的客户办理业务还是给等待区的客户办理业务,那得取决于选择什么队列。这时候客户8来了~ (4) 由于柜员1,2,3,4都在办理业务,并且等待区都坐满了,那么就关门拒绝给客户办理业务。 (5) 客户1,2业务办理结束,继续办理等待区客户的业务。 (6) 继续办理等待区客户业务,但是柜员4等了10分钟都没有客户要办理业务,于是他就去休息区休息了。 (7) 当所有客户的业务都办理完毕,工作区还需要留下两个人。 根据上面的图例,我们发现客户9被拒绝后走掉了。那就说明我们人员或者设置的座位大小不正确。如果你是这个公司的总裁,在这个公司分部怎样设置你觉的最合理呢?欢迎补充。 1. 分部店面(服务器)大小合适,节约成本。 2. 柜员(线程池中工作线程)尽量处于工作状态,但是不能累坏(我就是柜员[微笑])。 3. 座位(队列)尽量都坐满。 4. 不要让任何客户被拒之门外。 五、实际应用说之前,先来说说Java提供的三种创建线程池的工具(注意:自己做Demo可用,做项目咱不用) 1. Executors.newScheduledThreadPool(); 2. Executors.newWorkStealingPool(); 3. Executors.newFixedThreadPool(); 阿里编码规约曾说: 【强制】线程池不允许使用Executors去创建, 而是通过ThreadPoolExecutor的方式, 这样的处理方式让写的同学更加明确线程池的运行规则, 规避资源耗尽的风险。 说明:Executors返回的线程池对象的弊端如下: 1)FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。 2)CachedThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。 3)ScheduledThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。 我的天这要是上线了,搞不好年终奖与你擦肩而过。 1. 案例一(1) 场景 接下来就是编码时间,就简单介绍下ThreadPoolExecutor在代码中的使用。 (2) 代码:git地址 a. application.yml # 线程池 task: threadPool: # 核心线程 corePoolSize: 4 # 最大线程 maximumPoolSize: 8 # 等待工作的超时时间 (秒) keepAliveTime: 60 # 队列长度 cacheQueueLen: 20b. ThreadPool import lombok.Setter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.concurrent.*; /** * 线程池 * 注解: @ConfigurationProperties和@Setter一起使用, 否则成员变量无法赋值 * * @author wxy * @date 2023-02-12 */ @Component @Setter @ConfigurationProperties(prefix = "thread.pool") public class ThreadPool { private static final Logger LOGGER = LoggerFactory.getLogger(ThreadPool.class); private ExecutorService executorService; private int queueCapacity; private int corePoolSize; private int maximumPoolSize; private int keepAliveTime; /** * 初始化线程池 * 调用时间: SpringBoot项目启动自动调用 */ @PostConstruct public void init() { LOGGER.info("thread pool init start"); if (corePoolSize |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |