概述

  1. 线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销, 进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理 者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代 价。线程池不仅能够保证内核的充分利用,还能防止过分调度。

  2. 过程: 线程池做的工作主要是控制运行的线程数量,处理过程中将任 务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量, 超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。

  1. 特点
  • 降低资源消耗: 通过重复利用已创建的线程降低线程创建和销毁造成的销耗。
  • 提高响应速度: 当任务到达时,任务可以不需要等待线程创建就能立即执行。
  • 提高线程的可管理性: 线程是稀缺资源,如果无限制的创建,不仅会销耗系统资 源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

    架构

    Java 中的线程池是通过 Executor 框架实现的,该框架中用到了 Executor,Executors, ExecutorService,ThreadPoolExecutor 这几个类

image.png

线程池的种类与创建

newFixedThreadPool(常用)

  1. ExecutorService threadPool1 = Executors.newFixedThreadPool(5); //5个窗口
  2. 一池N线程
  3. ======================================
  4. 特征:
  5. 线程池中的线程处于一定的量,可以很好的控制线程的并发量
  6. 线程可以重复被使用,在显示关闭之前,都将一直存在
  7. 超出一定量的线程被提交时候需在队列中等待
  8. ==============================================
  9. 场景: 适用于可以预测线程数量的业务中,或者服务器负载较重,对线程数有严
  10. 格限制的场景

newSingleThreadExecutor

  1. ExecutorService threadPool2 = Executors.newSingleThreadExecutor(); //一个窗口
  2. 一池一线程
  3. ==========================================================
  4. 特征: 线程池中最多执行 1 个线程,之后提交的线程活动将会排在队列中以此
  5. 执行
  6. ===================================================
  7. 场景: 适用于需要保证顺序执行各个任务,并且在任意时间点,不会同时有多个
  8. 线程的场景

newCachedThreadPool()

  1. ExecutorService threadPool3 = Executors.newCachedThreadPool();
  2. 可扩容,根据需求创建线程
  3. ==================================
  4. 特点:
  5. 线程池中数量没有固定,可达到最大值(Interger. MAX_VALUE
  6. 线程池中的线程可进行缓存重复利用和回收(回收默认时间为 1 分钟)
  7. 当线程池中,没有可用线程,会重新创建一个线程
  8. ===========================================
  9. 场景: 适用于创建一个可无限扩大的线程池,服务器负载压力较轻,执行时间较 短,任务多的场景

使用

  1. //演示线程池三种常用分类
  2. public class ThreadPoolDemo1 {
  3. public static void main(String[] args) {
  4. //一池五线程
  5. ExecutorService threadPool1 = Executors.newFixedThreadPool(5); //5个窗口
  6. //一池一线程
  7. ExecutorService threadPool2 = Executors.newSingleThreadExecutor(); //一个窗口
  8. //一池可扩容线程
  9. ExecutorService threadPool3 = Executors.newCachedThreadPool();
  10. //10个顾客请求
  11. try {
  12. for (int i = 1; i <=10; i++) {
  13. //执行
  14. threadPool3.execute(()->{
  15. System.out.println(Thread.currentThread().getName()+" 办理业务");
  16. });
  17. }
  18. }catch (Exception e) {
  19. e.printStackTrace();
  20. }finally {
  21. //关闭
  22. threadPool3.shutdown();
  23. }
  24. }
  25. }

底层原理

构造方法

  1. //都是通过newThreadPoolExecutor,只是不同种类的线程池其参数不同
  2. public static ExecutorService newFixedThreadPool(){
  3. return new ThreadPoolExecutor(...);
  4. }
  5. ===============newThreadPoolExecutor构造器======================
  6. public ThreadPoolExecutor(int corePoolSize,
  7. int maximumPoolSize,
  8. long keepAliveTime,
  9. TimeUnit unit,
  10. BlockingQueue<Runnable> workQueue) {
  11. this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
  12. Executors.defaultThreadFactory(), defaultHandler);
  13. }

执行过程

image.png
当提交任务数大于 corePoolSize 的时候,会优先将任务放到 workQueue 阻 塞队列中。当阻塞队列饱和后,会扩充线程池中线程数,直到达到 maximumPoolSize 最大线程数配置。此时,再多余的任务,则会触发线程池 的拒绝策略了。
在阻塞队列满了之后,若还需要新线程则会进行线程池的扩充,此时会优先处理6,而不会去处理阻塞队列中的5

线程池的七个参数

image.png

  1. /**
  2. * 固定长度线程池
  3. * @return
  4. */
  5. public static ExecutorService newFixedThreadPool(){
  6. /**
  7. * corePoolSize 线程池的核心线程数
  8. * maximumPoolSize 能容纳的最大线程数
  9. * keepAliveTime 空闲线程存活时间
  10. * unit 存活的时间单位
  11. * workQueue 存放提交但未执行任务的队列 即阻塞队列
  12. * threadFactory 创建线程的工厂类:可以省略
  13. * handler 等待队列满后的拒绝策略:可以省略
  14. */
  15. return new ThreadPoolExecutor(10,
  16. 10,
  17. 0L,
  18. TimeUnit.SECONDS,
  19. new LinkedBlockingQueue<>(),
  20. Executors.defaultThreadFactory(),
  21. new ThreadPoolExecutor.AbortPolicy());
  22. }
  23. ==================================================
  24. /**
  25. * 单一线程池
  26. * @return
  27. */
  28. public static ExecutorService newSingleThreadExecutor(){
  29. /**
  30. * corePoolSize 线程池的核心线程数
  31. * maximumPoolSize 能容纳的最大线程数
  32. * keepAliveTime 空闲线程存活时间
  33. * unit 存活的时间单位
  34. * workQueue 存放提交但未执行任务的队列
  35. * threadFactory 创建线程的工厂类:可以省略
  36. * handler 等待队列满后的拒绝策略:可以省略
  37. */
  38. return new ThreadPoolExecutor(1,
  39. 1,
  40. 0L,
  41. TimeUnit.SECONDS,
  42. new LinkedBlockingQueue<>(),
  43. Executors.defaultThreadFactory(),
  44. new ThreadPoolExecutor.AbortPolicy());
  45. }
  46. =================================================
  47. /**
  48. * 可缓存线程池
  49. * @return
  50. */
  51. public static ExecutorService newCachedThreadPool(){
  52. /**
  53. * corePoolSize 线程池的核心线程数
  54. * maximumPoolSize 能容纳的最大线程数
  55. * keepAliveTime 空闲线程存活时间
  56. * unit 存活的时间单位
  57. * workQueue 存放提交但未执行任务的队列
  58. * threadFactory 创建线程的工厂类:可以省略
  59. * handler 等待队列满后的拒绝策略:可以省略
  60. */
  61. return new ThreadPoolExecutor(0,
  62. Integer.MAX_VALUE,
  63. 60L,
  64. TimeUnit.SECONDS,
  65. new SynchronousQueue<>(),
  66. Executors.defaultThreadFactory(),
  67. new ThreadPoolExecutor.AbortPolicy());
  68. }
  69. ====================================

关于拒绝策略

image.png
线程池中,有三个重要的参数,决定了拒绝策略:corePoolSize - 核心线 程数,也即最小的线程数。workQueue - 阻塞队列 。 maximumPoolSize - 最大线程数

当提交的任务数大于(workQueue.size() + maximumPoolSize ),就会触发线程池的拒绝策略。

自定义线程池(常用)

实际中多使用自定义线程池:
FixedThreadPool 和 SingleThreadExecutor 底层都是用 LinkedBlockingQueue 实现的,这个队列最大长度为 Integer.MAX_VALUE, 容易导致 OOM
image.png
自定义线程池

  1. //自定义线程池创建
  2. public class ThreadPoolDemo2 {
  3. public static void main(String[] args) {
  4. ExecutorService threadPool = new ThreadPoolExecutor(
  5. 2,
  6. 5,
  7. 2L,
  8. TimeUnit.SECONDS,
  9. new ArrayBlockingQueue<>(3),
  10. Executors.defaultThreadFactory(),
  11. new ThreadPoolExecutor.AbortPolicy()
  12. );
  13. //10个顾客请求
  14. try {
  15. for (int i = 1; i <=10; i++) {
  16. //执行
  17. threadPool.execute(()->{
  18. System.out.println(Thread.currentThread().getName()+" 办理业务");
  19. });
  20. }
  21. }catch (Exception e) {
  22. e.printStackTrace();
  23. }finally {
  24. //关闭
  25. threadPool.shutdown();
  26. }
  27. }
  28. }