1. 参考文献:
    1. 线程池的基本信息介绍:https://blog.csdn.net/ye17186/article/details/89467919
    2. 池化思想:https://class.imooc.com/lesson/1257#mid=34651

  1. Policy:策略 Synchronous:同步的 Transfer:迁移、转移、转让
  2. terminated:终止
  3. block:阻塞 blocking:阻塞
  4. Scheduled:预先安排的、预订

1. 为什么使用线程池

1.1 池化思想

  1. 线程池可以认为是更普遍的 “池化资源” 技术的一个经典示例
  2. 其产生的原因是,由于创建 / 销毁对象需要对内存资源或者其他相关资源进行相对操作
  3. 所以某些消耗资源的对象的创建和销毁是比较费事的(java 虚拟机还需要管理每一个对象的生命周期)
  4. 所以资源优化的一个方向就是减少创建和销毁对象的次数,尽可能地复用。比如说数据库连接池…

    1.2 答案

  5. 属于面试题,还有一些变相的提问

    1. 使用线程池的好处有哪些?
    2. 线程池有什么优势?
    3. ……..
  6. 标准的答案

    1. 降低资源的消耗:重用已经存在的线程,从而减少对象的创建以及销毁的开销,使用线程池可以有效的控制并发,从而提高资源利用率,也可以避免过多的资源竞争,提升性能
    2. 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行
    3. 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控

      2. 线程池的核心参数

      2.1 核心线程数

  7. 参数名称:corePoolSize

  8. 线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态
  9. 它们也不会被销毁,除非设置了allowCoreThreadTimeOut = true,则核心线程会随着设定的存活时间截止而销毁

    2.2 线程池最大线程数

  10. 参数名称:maximunPoolSize

  11. 任务被提交到线程池以后,首先会找有没有空闲存活线程,如果有则直接将任务交给这个空闲线程来执行
  12. 如果没有则会缓存到工作队列中,如果工作队列满了,才会创建一个新线程
  13. 然后从工作队列的头部取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列尾部
  14. 线程池不会无限制的去创建新线程,有一个最大线程数量的限制,这个数量即由maximunPoolSize指定

    2.3 线程存活时间

  15. 线程名称:keep Alive Time

  16. 一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize设置的数量
  17. 那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定
  18. 如果核心线程设置allowCoreThreadTimeOut,也代表核心线程的存活时间

    2.4 存活时间单位

  19. 就是字面意思,无需理解

    2.5 工作队列

    2.5.1 BlockingQueue详解

  20. BlockingQueue是什么?BlockQueue也叫阻塞队列,是java并发包里面非常常用的一个接口

  21. 阻塞队列有什么特点?当队列为空的时候,获取对象会阻塞;当队列满的时候放入对象会阻塞
  22. BlockQueue的作用?实现了队列该有的基本功能,多线程环境下自动的管理线程的等待和唤醒
  23. BlockQueue的核心api,具体查看项目java-baseimage.png

    2.5.2 work queue的特性

  24. 参数名称:work queue

  25. 有界概念:有界代表队列的容量是有上限的,无界则是相反(有界的数组可以防止资源耗尽问题)
  26. 存储等待执行的任务,新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务
  27. jdk中提供了六种工作队列(见下面详解) | 类名 | 有界 | 特性 | | —- | —- | —- | | ArrayBlockingQueue | 有 |
    1. 内部基于数组实现的,按FIFO排序,初始化的时必须指定容量大小,并且一单指定就无法修改了
    1. 初始化:新任务进来后,放进队列,然后则会有一个线程执行该任务
    1. 当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,创建一个新的线程来调用
    1. 如果队列未满的,线程数量已经达到maxPoolSize,新的任务则会发在队列尾部等待执行
    1. 如果队列已经是满的,线程数量已经达到maxPoolSize,则会执行拒绝策略
    | | LinkedBlockingQueue | 有/无 |
    1. 底层基于链表实现的,按照FIFO排序
    1. 可有界可无界:初始化指定容量就是有界;初始化不指定容量默认设置为Interger.MAX就是无界(原因:因为正常情况下如果你的容量达到Interger.MAX,代表你的jvm已经崩掉了,所以无界)
    1. 当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize,因此使用该工作队列时,参数maxPoolSize其实是不起作用的
    | | ProorityBlockingQueue | 无 |
    1. 带有优先级的阻塞队列,优先级通过参数Comparator实现
    1. 允许插入null对象
    1. 元素必须实现Comparable接口,队列的排序规则要使用
    | | SynchronousQueue | 有 |
    1. 不存储元素,内部容量为0
    1. 一个线程发起插入操作后,就会被阻塞,直到另外一个线程发起相应的删除操作才会恢复,因此又称为同步队列
    1. 主要实现了tack()和put()操作,且需要配对使用
    1. 生产者放入一个任务必须等到消费者取出这个任务,也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略
    | | DelayQueue | 无 |
    1. 其中的元素必须实现delayed接口
    1. 其中的元素必须实现排序,一般按过期时间优先排序
    1. 使用场景:定时关闭连接、缓存对象、超时处理场景
    | | LinkedTransferQueue | 无 |
    1. 底层基于链表
    1. 用于数据交换
    1. 比其他队列多了transfer()和trytransfer()方法
    |

2.6 线程工厂

  1. 参数名称:thread Factory
  2. 创建一个新线程时使用的工厂,可以用来设定线程名、是否为守护线程(daemon)线程……

    1. defaultThreadFactory:默认使用,创建的线程拥有默认的优先级、非守护线程、有线程名称
    2. privilegedThreadFactory:在默认的基础上,可以让运行这个线程的任务和这个线程拥有相同的访问控制和ClassLoader

      2.7 拒绝策略

  3. 参数名称:handler

  4. 当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略,就是解决这个问题的,jdk中提供了4种拒绝策略

    1. AbortPolicy:默认策略,直接丢弃任务,并抛出RejectedExecutionException异常。
    2. CallerRunsPolicy:在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
    3. DiscardPolicy:直接丢弃任务,什么都不做。
    4. DiscardOldestPolicy:抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列,并且优先执行新进入的任务

      3. 线程池常用api展示

  5. 查看项目java-base关于线程池的文件夹

    4. 线程池的状态

    image.png

  6. running:可以接收新的任务,也可以处理队列中的任务

  7. shutdown:不能接收新的任务,但是可以处理阻塞队列中的任务
  8. stop:线程池停止
  9. tidying:中间状态,暂时无意义
  10. terminated:终止状态

    5. Executors.class

    executors类提供了,一系列的常见的线程池类型,拿出来直接使用 下面的题目定义是根据返回的类型来定义的,前两种都还是ThreadPool大类

5.1 ThreadPoolExecutor

各个类之间关系 ThreadPoolExecutor extends AbstractExecutorService implements ExecutorService extends Executor

5.1.1 SingleThreadExecutor

  1. ExecutorService singleThreadExecutor = Executors._newSingleThreadExecutor_();
  2. 创建一个单线程的线程池,也就是说任何时间线程池中只有一个线程,保证任务按照指定顺序执行
  3. 如果该线程因为异常而结束,那么会有一个新的线程来替代它
  4. 适用于需要严格控制执行顺序的场景

    5.1.2 FixedThreadPool

  5. ExecutorService fixedThreadPool = Executors._newFixedThreadPool_(1);

  6. 创建固定大小的线程池,任意时间线程池中的线程数目是固定的
  7. 每次提交一个任务就创建一个线程,直到线程达到线程池的最大值,一旦达到最大值就会保持不变
  8. 如果某个线程因为执行异常而结束,那么线程池会补充一个新线程
  9. 适用于线程数比较固定的并发场景

    5.1.3 CachedThreadPool

  10. ExecutorService cachedThreadPool = Executors._newCachedThreadPool_();

  11. 创建一个可缓存的线程池,该线程池没有核心线程,创建新的线程之前会查看是否有以前使用的线程
  12. 如果线程池的大小超过了处理任务所需要的线程,就会回收部分空闲(默认 60 秒不执行任务)的线程
  13. 当任务数增加时,此线程池又可以智能的添加新线程来处理任务
  14. 此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者JVM)能够创建的最大线程
  15. 适用于生存周期很短的异步任务

    5.2 ScheduleThreadPoolExecutor

  16. 继承自ThreadPoolExecutor所以满足线程池的一切特性

  17. 支持延迟执行任务、周期性的执行任务
  18. 具体查看java-base项目
  19. 它和Timer的区别

    1. 建议优先使用线程池(Timer的缺点)
      1. 只支持单线程
      2. 如果一个任务执行的比较就,就会影响到下一个任务的执行
      3. 如果一个任务发送异常,会导致其他任务无法执行

        5.3 ForkJoinPool

        各个类之间关系 ForkJoinPool extends AbstractExecutorService implements ExecutorService extends Executor

  20. jdk1.7开始提供的一个新的线程池

  21. 把一个大的任务拆分成若干个小的任务(fork过程)把每个小任务的结果进行汇总(join的过程)
  22. 比较适合分而治之,递归计算的cpu密集场景,实现工作窃取算法

    6. 线程池的实际应用

  23. Rabbit MQ可靠性消息投递组件中的消息发送使用多线程异步发送

  24. Redis Lock + ScheduledThreadPool,实现简单版分布式定时任务,实现定时推送未结案的赔案
  25. Spring Task也是用的线程池实现的定时任务
  26. Eureka Client里面,每隔三十秒发送个心跳,其实就是个定时任务型的线程池
  27. @Async实现异步的时候,底层也是个线程池

    7. 线程池的调优

    7.1 线程池优化方向

  28. 线程数:关注如何设置线程数即可

  29. 工作队列的大小

    1. 关注单个任务占用内存
    2. 关注线程池计划占用内存

      7.2 线程数优化

  30. 字母代表的含义

    1. n:集群cpu核心数
    2. u:期望的cpu利用率
    3. wt:线程等待时间
    4. st:线程运行时间
  31. cpu密集型任务:大量的计算、排序….
    1. 线程数:经验公式 n + 1
  32. io密集型任务:操作数据库的过程、大量增删改查
    1. 线程数:经验公式 2n
  33. 混合型任务:上面两种都占有
    1. 线程数n * U * (1+ wt/st )
  34. 通过jvisualvm分析项目运行

    1. 自用时间 = 线程运行时间 = st
    2. 总时间 - 自用时间 = 线程等待时间 = wt

      7.3 工作队列优化

      …..

      7.4 优化的流程

  35. 评估业务是哪种类型的,给出期望优化

  36. 结合压测 + 计算出的理论值,不断在周边调整参数,结合坐标图得出

    8. 异步化

第一个:同步阻塞
第二个:异步费阻塞
image.png
image.png

如何实现本地异步操作:
新搞一个线程()
线程池
@Async:spring提供的,使用动态代理 + 线程池实现的
消息队列