# 并发工具类:线程池执行流程

在这里插入图片描述

# 线程池的核心参数

上一节我们已经自己实现了一个线程池,本节我们看看JDK提供的线程池是如何实现的?

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

其实JDK提供的线程池创建和执行过程和我们的基本一样。你看构造函数都一摸一样

参数 含义
corePoolSize 核心线程数
maximumPoolSize 最大线程数
keepAliveTime 非核心线程的空闲时间
TimeUnit 空闲时间的单位
BlockingQueue<Runnable> 任务队列
ThreadFactory 线程工厂
RejectedExecutionHandler 拒绝策略

看一下RejectedExecutionHandler,ThreadPoolExecutor内置了四种实现

策略
AbortPolicy 丢弃任务,抛运行时异常(默认的处理策略)
CallerRunsPolicy 用放任务的线程执行任务(相当于就是同步执行了)
DiscardPolicy 忽视,什么都不会发生
DiscardOldestPolicy 丢弃队列里最近的一个任务,并执行当前任务

# 执行任务

在这里插入图片描述

  1. 线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行他们。

  2. 当调用execute()方法添加一个任务时,线程池会做如下判断:

    a)如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务

    b)如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列

    c)如果这时候队列满了,而且正在运行的线程数量小于maximunPoolSize,那么还是要创建非核心线程立刻运行这个任务

    d)如果队列满了,而且正在运行的线程数量大于或等于maximunPoolSize,那么线程池会根据拒绝策略来处理任务

  3. 当一个线程完成任务时,它会从队列中取下一个任务来执行

  4. 当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小

# 使用线程池的好处

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

# 如何合理设置线程池的参数

如何设置核心线程数和最大线程数

任务类型 场景 核心线程数 最大线程数
cpu密集型 复杂的数学运算,加解密 n + 1 n+1
io密集型 web服务,大量设计网络请求,数据查询 2 * cpu 核心线程数的1.5到2倍

n为服务器的cpu核数,加1是为了某个线程意外暂停时,这个额外的线程能顶上,保证CPU不闲着

阻塞队列严禁使用无界队列,必须使用有界队列 队列容量 = (核心线程数 / 单个任务耗时) * 系统容忍的最大响应时间 例如,核心线程能每秒处理100个任务,系统最多允许等2秒,那队列设为200比较合理

世界上没有完美的静态公式。 生产环境的流量是瞬息万变的,最好的方案是动态调节

不要写死参数:利用 Java ThreadPoolExecutor 提供的 setCorePoolSize() 和 setMaximumPoolSize() 方法,将参数对接到配置中心(如 Nacos、Apollo)。

必须配套监控:实时监控线程池的以下指标:

  1. getPoolSize():当前线程数
  2. getActiveCount():正在干活的线程数
  3. getQueue().size():队列中排队的任务数

动态调优:如果发现任务经常排队,说明队列长了或核心线程少了;如果经常触发拒绝策略,说明最大线程或队列扛不住了,需要根据监控调大参数或进行业务限流

# submit() 和 execute() 的区别

对比维度 execute submit
接收的参数 只能接收 Runnable 类型的任务 可以接收 Runnable,也能接收 Callable(带返回值)
返回值 没有返回值(void) 返回一个 Future对象,用于获取执行结果或取消任务
异常处理行为 如果任务抛出未捕获异常,线程会直接崩掉,异常会直接打印到日志 任务抛出异常会被吞掉并暂存,只有当调用 Future.get() 时才会抛出 ExecutionException
异常处理的不同 如果任务内部发生异常且没有显式 try-catch,执行这个任务的线程会直接死掉,线程池会默默地重新创建一个新线程来补上空缺 执行任务的线程不会死,它会被完好地保留下来复用