【小家java】用 ThreadPoolExecutor/ThreadPoolTaskExecutor 线程池技术提高系统吞吐量(附带线程池参数详解和使用注意事项)(上)

简介: 【小家java】用 ThreadPoolExecutor/ThreadPoolTaskExecutor 线程池技术提高系统吞吐量(附带线程池参数详解和使用注意事项)(上)

相关阅读


【小家java】java5新特性(简述十大新特性) 重要一跃

【小家java】java6新特性(简述十大新特性) 鸡肋升级

【小家java】java7新特性(简述八大新特性) 不温不火

【小家java】java8新特性(简述十大新特性) 饱受赞誉

【小家java】java9新特性(简述十大新特性) 褒贬不一

【小家java】java10新特性(简述十大新特性) 小步迭代

【小家java】java11新特性(简述八大新特性) 首个重磅LTS版本

【小家java】Java中的线程池,你真的用对了吗?(教你用正确的姿势使用线程池)

小家Java】一次Java线程池误用(newFixedThreadPool)引发的线上血案和总结

【小家java】BlockingQueue阻塞队列详解以及5大实现(ArrayBlockingQueue、DelayQueue、LinkedBlockingQueue…)


每篇一句

烧不死的鸟才是火凤凰,烧死了就变成烤乳鸽了。


1、概述


在Java中,我们一般通过集成Thread类和实现Runnnable接口,调用线程的start()方法实现线程的启动。但如果并发的数量很多,而且每个线程都是执行很短的时间便结束了,那样频繁的创建线程和销毁进程会大大的降低系统运行的效率。线程池正是为了解决多线程效率低的问题而产生的,他使得线程可以被复用,就是线程执行结束后不被销毁,而是可以继续执行其他任务。(这里可以用tomcat做例子进行思考)


很多人想问,线程池听起来高大上,但在实际工作中却很少使用。其实不然,在各种流行框架或者高性能的架构中,池化技术是无处不在的。所以有人就想问了,线程池有什么用呢?


一言以蔽之,就是提高系统效率和吞吐量。如果服务器对每个请求都分别创建一个线程的话,在很短时间内就会产生很多创建和销毁的动作,然而服务器在创建和销毁线程上花费的时间和消耗的系统资源都相当大。线程池就可以尽量减少这种情况的发生。

因此java.util.concurrent.ThreadPoolExecutor这个类(java5以后才出现,由大师 Doug Lea 完成的),我们就不能不讲了,它就是今天的主菜


2、栗子


一、ThreadPoolExecutor的重要参数


corePoolSize:核心线程数


核心线程会一直存活,即使没有任务需要执行


     当线程数小于核心线程数时(还未满,就会一直增),即使有线程空闲,线程池也会优先创建新线程处理


     设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭

queueCapacity:任务队列容量(阻塞队列)


当核心线程数达到最大时,新任务会放在队列中排队等待执行

maxPoolSize:最大线程数


当线程数>corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务,直到线程数量达到maxPoolSize

当线程数已经=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常

keepAliveTime:线程空闲时间


当线程空闲时间达到keepAliveTime时,线程会被销毁,直到线程数量=corePoolSize

如果allowCoreThreadTimeout=true,则会直到线程数量=0(这个特性需要注意)


allowCoreThreadTimeout:允许核心线程超时(如上,会影响keepAliveTime哦)

rejectedExecutionHandler:任务拒绝处理器(用户可以自定义拒绝后的处理方式)


两种情况会拒绝处理任务:

1、当线程数已经达到maxPoolSize,且任务队列已满时,会拒绝新任务

2、当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务(并不是立马停止,而是执行完再停止)。

若拒绝后,此时,线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置,默认值是AbortPolicy,会抛出异常

hreadPoolExecutor类有几个内部实现类来处理这类情况:

1: AbortPolicy 丢弃任务,抛运行时异常

2:CallerRunsPolicy 执行任务(这个策略重试添加当前的任务,他会自动重复调用 execute() 方法,直到成功) 如果执行器已关闭,则丢弃.

3:DiscardPolicy 对拒绝任务直接无声抛弃,没有异常信息

4:DiscardOldestPolicy 对拒绝任务不抛弃,而是抛弃队列里面等待最久的(队列头部的任务将被删除)一个线程,然后把拒绝任务加到队列(Queue是先进先出的任务调度算法,具体策略会咋下面有分析)(如果再次失败,则重复此过程)

5:实现RejectedExecutionHandler接口,可自定义处理器(可以自己实现然后set进去)


二、ThreadPoolExecutor处理任务的顺序、原理


一个任务通过 execute(Runnable) 方法被添加到线程池,任务就是一个 Runnable 类型的对象,任务的执行方法就是 Runnable 类型对象的 run() 方法。


当一个任务通过 execute(Runnable) 方法欲添加到线程池时,线程池采用的策略如下(即添加任务的策略):


  1. 如果此时线程池中的数量小于 corePoolSize ,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
  2. 如果此时线程池中的数量等于 corePoolSize ,但是缓冲队列 workQueue 未满,那么任务被放入缓冲队列。
  3. 如果此时线程池中的数量大于 corePoolSize ,缓冲队列 workQueue 满,并且线程池中的数量小于maximumPoolSize ,建新的线程来处理被添加的任务。
  4. 如果此时线程池中的数量大于 corePoolSize ,缓冲队列 workQueue 满,并且线程池中的数量等于maximumPoolSize ,那么通过 handler 所指定的策略来处理此任务。


任务处理的优先级(顺序)为:


核心线程 corePoolSize 、任务队列 workQueue 、最大线程 maximumPoolSize ,如果三者都满了,使用 handler处理被拒绝的任务。当线程池中的线程数量大于 corePoolSize 时,如果某线程空闲时间超过 keepAliveTime ,线程将被终止。这样,线程池可以动态的调整池中的线程数。


简要概括如下:

当线程数小于核心线程数时,创建线程。

当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。

当线程数大于等于核心线程数,且任务队列已满

若线程数小于最大线程数,创建线程

若线程数等于最大线程数,抛出异常,拒绝任务


线程池处理流程图:

image.png


这里提醒各位:如果失败处理策略选择了DiscardOldestPolicy,你是有可能丢掉任务的哦。


另外 Executors 类里面还有几个方法:newFixedThreadPool(),newCachedThreadPool() 等几个方法,实际上也是间接调用了ThreadPoolExocutor ,不过是传的不同的构造参数。

相关文章
|
23天前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
101 38
|
21天前
|
Java
线程池内部机制:线程的保活与回收策略
【10月更文挑战第24天】 线程池是现代并发编程中管理线程资源的一种高效机制。它不仅能够复用线程,减少创建和销毁线程的开销,还能有效控制并发线程的数量,提高系统资源的利用率。本文将深入探讨线程池中线程的保活和回收机制,帮助你更好地理解和使用线程池。
46 2
|
23天前
|
Prometheus 监控 Cloud Native
JAVA线程池监控以及动态调整线程池
【10月更文挑战第22天】在 Java 中,线程池的监控和动态调整是非常重要的,它可以帮助我们更好地管理系统资源,提高应用的性能和稳定性。
60 4
|
23天前
|
Prometheus 监控 Cloud Native
在 Java 中,如何使用线程池监控以及动态调整线程池?
【10月更文挑战第22天】线程池的监控和动态调整是一项重要的任务,需要我们结合具体的应用场景和需求,选择合适的方法和策略,以确保线程池始终处于最优状态,提高系统的性能和稳定性。
94 2
|
1月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
43 1
C++ 多线程之初识多线程
|
26天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
19 3
|
26天前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
16 2
|
26天前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
28 2
|
26天前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
28 1
|
26天前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
34 1