• 什么是线程池


    第一版

    假设有一段代码,你希望异步执行它,是不是要写出这样的代码?

    new Thread(r).start();

    这种写法当然可以完成功能,可是你这样写,老王这样写,老张也这样写,程序中到处都是这样创建线程的方法,需要写一个统一的工具类让大家调用

    1 // 新线程:直接创建一个新线程运行
    2 class FlashExecutor implements Executor {
    3     public void execute(Runnable r) {
    4         new Thread(r).start();
    5     }
    6 }

    第二版

    假如有 10000 个人都调用这个工具类提交任务,那就会创建 10000 个线程来执行,这肯定不合适!要控制一下线程的数量。

    可以把这个任务 r 丢到一个 tasks 队列中,然后只启动一个线程,就叫它Worker 线程吧,不断从 tasks 队列中取任务,执行任务。

    这样无论调用者调用多少次,永远就只有一个 Worker 线程在运行,像这样。

    这个设计有了三个重大的意义:

    1. 控制了线程数量。

    2. 队列不但起到了缓冲的作用,还将任务的提交与执行解耦了。

    3. 最重要的一点是,解决了每次重复创建和销毁线程带来的系统开销。

    第三版

    只有一个线程在某些场景下是很吃力的,把 Worker 线程的数量增加,但是具体数量要让使用者决定,调用时传入,就叫核心线程数 corePoolSize 吧。

    1. 初始化线程池时,直接启动 corePoolSize 个工作线程 Worker 先跑着。

    2. 这些 Worker 就是死循环从队列里取任务然后执行。

    3. execute 方法仍然是直接把任务放到队列,但队列满了之后直接抛弃。

    现在我们已经实现了一个至少不那么丑陋的线程池了,但还有几处小瑕疵,

    比如初始化的时候,就创建了一堆 Worker 线程在那空跑着,假如此时并没有异步任务提交过来执行,这就有点浪费了。

    第四版

    现在我们做出如下改进:

     就像下面这样:

    第五版(终版)

    弹性就是在任务提交比较频繁,和任务提交非常不频繁这两种情况下,这个代码是有问题的。

    当提交任务的量突增时,工作线程和队列都被占满了,就只能走拒绝策略,其实就是被丢弃掉。

    调用方可以通过设置很大的核心线程数 corePoolSize 来解决这个问题呀。

    的确是可以,但一般场景下 QPS 高峰期都很短,而为了这个很短暂的高峰,设置很大的核心线程数,简直太浪费资源了。

    我们可以发明一个新的属性,叫最大线程数 maximumPoolSize

    当核心线程数和队列都满了时,新提交的任务仍然可以通过创建新的工作线程(叫它非核心线程),

    直到工作线程数达到 maximumPoolSize 为止,这样就可以缓解一时的高峰期了,而用户也不用设置过大的核心线程数。

    具体操作演示:

    文字说明:

    1. 开始的时候和上一版一样,当 workCount < corePoolSize 时,通过创建新的 Worker 来执行任务。

    2. 当 workCount >= corePoolSize 就停止创建新线程,把任务直接丢到队列里。

    3. 但当队列已满且仍然 workCount < maximumPoolSize 时,不再直接走拒绝策略,而是创建非核心线程,直到 workCount = maximumPoolSize,再走拒绝策略。

    corePoolSize 就负责平时大多数情况所需要的工作线程数,而 maximumPoolSize 就负责在高峰期临时扩充工作线程数。

    高峰时期的弹性搞定了,那自然就还要考虑低谷时期。当长时间没有任务提交时,核心线程与非核心线程都一直空跑着,浪费资源。

    我们可以给非核心线程设定一个超时时间 keepAliveTime,当这么长时间没能从队列里获取任务时,就不再等了,销毁线程

    现在线程池在 QPS 高峰时可以临时扩容,QPS 低谷时又可以及时回收线程(非核心线程)而不至于浪费资源,真的显得十分 Q 弹。

    总结

    首先它的构造方法是这个样子:

     1 public FlashExecutor(
     2         int corePoolSize,
     3         int maximumPoolSize,
     4         long keepAliveTime,
     5         TimeUnit unit,
     6         BlockingQueue<Runnable> workQueue,
     7         ThreadFactory threadFactory,
     8         RejectedExecutionHandler handler) 
     9 {
    10     ... // 省略一些参数校验
    11     this.corePoolSize = corePoolSize;
    12     this.maximumPoolSize = maximumPoolSize;
    13     this.workQueue = workQueue;
    14     this.keepAliveTime = unit.toNanos(keepAliveTime);
    15     this.threadFactory = threadFactory;
    16     this.handler = handler;
    17 }

    这些参数分别是

    int corePoolSize:核心线程数

    int maximumPoolSize:最大线程数

    long keepAliveTime:非核心线程的空闲时间

    TimeUnit unit:空闲时间的单位

    BlockingQueue<Runnable> workQueue:任务队列(线程安全的阻塞队列)

    ThreadFactory threadFactory:线程工厂

    RejectedExecutionHandler handler:拒绝策略

    整个任务的提交流程是:

    来自:https://mp.weixin.qq.com/s/OKTW_mZnNJcRBrIFHONR3g

  • 相关阅读:
    Vue2.x源码学习笔记-Vue构造函数
    微服务从设计到部署(七)重构单体为微服务
    微服务从设计到部署(六)选择部署策略
    Spring REST API + OAuth2 + AngularJS
    微服务从设计到部署(五)事件驱动数据管理
    REST:JAX-RS 与 Spring
    微服务从设计到部署(四)服务发现
    了解 Spring Boot AutoConfiguration
    微服务从设计到部署(三)进程间通信
    微服务从设计到部署(二)使用 API 网关
  • 原文地址:https://www.cnblogs.com/ybbybb/p/14311073.html
Copyright © 2020-2023  润新知