重点理解线程池
目录
使用线程池的好处
线程池的七个参数
线程池提交任务的流程
任务拒绝策略有哪些,怎么选择?
核心线程数和最大线程数怎么设定?
为什么不推荐使用jdk的Executors创建线程池?
动态线程池 (扩展内容,待新增)
使用线程池的好处
通过使用线程池起到资源管理、线程复用的效果,避免频繁地创建和销毁线程带来的性能开销。
具体解释:线程的创建和销毁是一项开销较大的操作,频繁地创建和销毁线程会耗费大量的系统资源。通过使用线程池,可以事先创建一定数量的线程,并将它们放入池中,需要执行任务时直接从池子中获取复用线程。这样可以避免频繁地创建和销毁线程,提高系统的资源利用率。
线程池的七个参数
- 参数一:核心线程数量
- 参数二:最大线程数量
- 参数三:临时线程最大存活时间
- 参数四:临时线程最大存活时间的单位
- 参数五:任务阻塞队列
- 参数六:线程工厂,用于创建线程的工厂类
- 参数七:任务的拒绝策略
线程池提交任务的流程
- 提交任务时,会先让核心线程去执行任务
- 如果核心线程都在工作,则先将任务放到阻塞队列
- 如果阻塞队列满了,则会创建临时线程去执行任务(核心线程+临时线程 不能超过最大线程参数值)
- 如果达到最大线程数,就会触发拒绝策略
任务拒绝策略有哪些,怎么选择?
- AbortPolicy:这是默认的拒绝策略,会抛出RejectedExecutionException异常。新任务会被立即拒绝,不会加入到任务队列中,也不会执行。虽然它是默认策略,但实际场景中需谨慎使用。如果任务重要性高,直接抛出异常可能导致关键任务丢失(如订单支付、消息通知)。通常更适合允许部分任务失败的场景。
- DiscardPolicy:会丢弃新的任务而且不会抛出异常。新任务提交后会被默默地丢弃,不会有任何提示或执行。适用场景更偏向允许丢失少量任务且不影响系统整体逻辑的轻量级任务(如心跳上报、临时缓存更新)
- DiscardOldestPolicy:会先尝试将任务队列中最早/旧的任务删除,然后再尝试提交新任务。这个策略适用于一些实时性要求较高的场景,适用场景应强调新任务比旧任务更重要(如实时数据处理场景中,新数据比旧数据更有价值)
- CallerRunsPolicy:这个策略将任务回退给调用线程,而不会抛出异常。调用线程会尝试执行任务。这个策略可以降低任务提交速度,适合不希望任务丢失且能接受任务执行延迟的场景(让调用submit的线程自行执行任务)

核心线程数和最大线程数怎么设定?
应用类型
- CPU密集型:对于CPU密集型的任务 (如计算密集型任务),线程数公式:CPU核数+1,因为这些任务主要消耗CPU资源,CPU并不空闲无法切换线程上下文,所以线程数不需要这么多。
- I/O密集型:对于任务涉及大量网络IO或磁盘IO,则可以配置更多的线程,线程数公式:2*CPU核数,因为线程在IO阻塞等待时,CPU可以切换线程上下文去处理其他任务。
上述两个公式其实来源于这个公式: 线程数 = CPU核数 * (1 + 线程等待时间 / 线程运行总时间)
CPU密集型中线程等待时间无线趋近于0,则线程数 = CPU核数 上面说的CPU核数+1(备用线程)
IO密集型中线程等待时间无限趋近于线程总运行时间,所以线程数 = CPU核数 * (1+1)
这些公式只是推荐值,但是不建议硬套公式,因为一个应用中,可能有很多个线程池,除了线程池 也还有很多其他线程,再者说有很多请求涉及到IO操作和计算型操作,所以先根据公式/推荐值大致的设置一个数值,再进行压测 (例如多线程并发去请求对应的接口,查看执行完毕所有请求的耗时) ,结合实际业务情况来实践优化,最终达到一个相对合理的值,目标:尽可能让CPU达到更高的利用率,能够较快处理大量并发请求。
压测过程中,确定核心线程数的值,另外可以在压测效果差不多的情况下,选定最大线程数的值。
注意:设置的线程数并不是越多越好,频繁的线程上下文切换,反而可能导致系统性能降低
为什么不推荐使用jdk的Executors创建线程池?
Executors 是一个工厂类,它提供了用于创建不同类型线程池的静态方法
线程池不允许使用 Executors 去创建,而是通过ThreadPoolExecutor的方式,规避资源耗尽的风险
Executors 返回的线程池对象的弊端如下:
- newFixedThreadPool() 和 newSingleThreadPool()
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM
- newCachedThreadPool()
允许的创建线程数量为 Integer.MAX VALUE,可能会创建大量的线程,从而导致 OOM
动态线程池 (扩展内容,待新增)
前面说到一个应用中的情况是非常复杂的,很多的线程池,请求处理过程是IO和计算混合的
所以我们需要动态线程池,能够在线上运行服务的同时灵活去调整配置进行调优
思路:把几个核心参数暴露出来,将线程池的参数从代码中迁移到分布式配置中心上,实现线程池参数可动态配置和即时生效,根据线上线程池的监控,需要时去调整核心参数

