vlambda博客
学习文章列表

异步使用场景+线程池

01  异步场景


异步使用的场景:

  1. 文件导出时,要获取一系列操作:

    (1)先将前端传的文件上传到公司tos平台,将数据存入数据库。

    (2)定时任务扫数据库/也可以通过消息队列完成,调用机器人发送通知给用户:需要两次http请求:机器人要获取发送消息的token,然后机器人再调用http请求给用户发送消息。

    (1)和(2)串行操作的话,用户收到“已经导出完成,请到lark查看”等待时间会很长,并行的话,(1)执行完成后,用户就收到“已经导出完成,请到lark查看”,用户切换到lark的过程中,大概1s的时间,(2)就执行完成,用户就能看到导出的文件了。


可能存在的问题:用户看到了提示“已经导出完成,请到lark查看,但是切换到lark时会有两种情况:

(1) 没有及时看到文件,过了一会才看到,这可能是超时重试的结果

(2)用户没有看到问题,因为重试几次后都失败了,所以会在报警群里报警,这个时候就只能用户再重新导出了。


2. 线程池也是异步操作:

调用DWS时,先像池子里面提交任务,提交之后就里面返回,等到dept_task.result()时会一直轮训查询的结果。



02  为什么要用线程池


我们看看不使用线程池有哪些坏处。

  1. 频繁的线程创建和销毁会占用更多的CPU和内存

  1. 频繁的线程创建和销毁会对GC产生比较大的压力

  1. 线程太多,线程切换带来的开销将不可忽视

  1. 线程太少,多核CPU得不到充分利用,是一种浪费


使用线程池可以带来一系列好处:

  1. 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。

  2. 提高响应速度:任务到达时,无需等待线程创建即可立即执行。

  3. 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。

  4. 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。

03 线程池原理


  1. corePool -> 核心线程池

  2. maximumPool -> 线程池最大线程数

  3. BlockQueue -> 队列

  4. RejectedExecutionHandler -> 拒绝策略


  • corePoolSize就是线程池中的核心线程数量,这几个核心线程,在没有用的时候,也不会被回收。

  • maximumPoolSize就是线程池中可以容纳的最大线程的数量,

  • 而keepAliveTime,就是线程池中除了核心线程之外的其他的最长可以保留的时间,也就是非核心线程可以保留的最长的空闲时间,而util,就是计算这个时间的一个单位,

  • workQueue,就是等待队列,任务可以储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出)。

  • threadFactory,就是创建线程的线程工厂,最后一个handler,是一种拒绝策略,我们可以在任务满了之后,拒绝执行某些任务。


异步使用场景+线程池

public class ThreadPoolTest { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(5); for (int i = 0; i < 10; i++) { executor.submit(() -> { System.out.println("thread id is: " + Thread.currentThread().getId()); try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } }); } }}

运行结果:共用5个线程

thread id is: 12thread id is: 15thread id is: 11thread id is: 14thread id is: 13thread id is: 14thread id is: 13thread id is: 15thread id is: 12thread id is: 11


生命周期管理

线程池内部使用一个变量维护两个值:运行状态(runState)和线程数量 (workerCount)
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

高3位保存runState,低29位保存workerCount。

用一个变量去存储两个值,可避免在做相关决策时,出现不一致的情况,不必为了维护两者的一致,而占用锁资源

异步使用场景+线程池


04 任务执行机制


1. 任务调度:


2. 任务缓冲

线程池的本质是对任务和线程的管理,线程池中是以生产者消费者模式,通过一个阻塞队列来实现的。阻塞队列缓存任务,工作线程从阻塞队列中获取任务。
阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。

阻塞队列常用于生产 者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。


3. 任务申请

任务的执行有两种可能:
一种是任务直接由新创建的线程执行。
另一种是线程从任务队列中获取任务然后执行,执行完任务的空闲线程会再次去从队列中申请任务再去执行。
第一种情况仅出现 在线程初始创建的时候,第二种是线程获取任务绝大多数的情况。


4. 任务拒绝

线程池有一个最大的容量,当线程池的任务缓存队列已满,并且线程池中的线程数目达到maximumPoolSize时,就需要拒绝掉该任务,采取任务拒绝策略,保护线程池。



05 小结

  1. 使用线程池:

    1. 可以减少频繁创建销毁线程所占用的CPU和内存

    2. 提高相应速度

    3. 可以限制线程池可执行的最大线程数

  2. 线程池原理:核心线程池,最大线程数量,阻塞队列,拒绝策略,生命周期(运行状态+线程数量)

  3. 任务执行机制:任务调度,任务缓冲,任务申请,任务拒绝。


参考:

https://www.jianshu.com/p/7ab4ae9443b9

https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html