1,写在前面:

线程池是一种池化技术,核心思想就是复用线程,避免每次来一个任务就new一个Thread,因为创建线程和销毁线程的开销都不小,一个线程起码占用1MB左右的栈空间,还有操作系统层面的调度成本。

线程池有几个关键参数:核心线程数最大线程数空闲存活时间工作队列拒绝策略

2,工作流程

  1. 任务来了先看核心线程够不够用,不够就创建新线程处理。默认核心线程是懒创建的,有任务才new,不过可以通过prestartAllCoreThreads预热。
  2. 核心线程数满了之后,新任务不会立刻创建线程,而是先丢到工作队列里面排队
  3. 队列里面也塞满了,这时候才会创建非核心线程,最多创建到最大线程数。
  4. 队列满了,线程也到顶了,再来任务就触发拒绝策略
  5. 线程空闲时间超过keepAliveTime,并且当前线程数超过核心线程数,多余的线程会被回收。设置allowCoreThreadTimeOut为true的话连核心线程也能回收。

3,图解线程池执行流程

  • 任务提交,线程池线程数还未达到核心线程数:

  • 核心线程数已满,任务队列未满的情况:

  • 核心线程数已满,任务队列也满了的情况:

  • 线程池中线程数已达最大线程数的情况:

注意:核心线程和非核心线程在线程池内部压根没有标识区分,都是普通的Worker对象。图中区分只是为了说清楚创建顺序。

4,为什么是先排队而不是先加线程?

很多人一开始可能都会觉得很奇怪,任务多了为啥不直接加线程干活,非要先排队?

原因是线程不是免费的。每次创建一个线程起码占用1MB的栈空间,操作系统还要维护线程上下文、调度开销。如果任务突然来一波就疯狂创建线程,系统资源很快耗尽,CPU光切换上下文就忙不过来了。

队列的作用是削峰填谷。任务暂时堆在队列里,让现有线程慢慢消化。如果队列塞满了说明真扛不住了,这时候才加线程救急。

举个例子:餐厅有5个服务员正常情况下就够用,如果突然一下子来了30个客人,老板不会立马招25个临时工,而是让客人先排队等位。如果排队都排到门外了,才考虑加人。

5,核心参数的实际含义

ThreadPoolExecutor的7个核心参数:

  • corePoolSize:核心线程数,线程池平时维护的线程数量。即使这些线程空闲也不会被回收,除非设置了allowCoreThreadTimeOut。
  • maximumPoolSize:最大线程数,线程池能创建的线程上限。核心线程满了、队列满了才会创建到这个数。
  • keepAliveTime和unit:空闲线程的存活时间。超过核心线程数的那部分线程,空闲超过这个时间就会被回收。
  • workQueue:任务队列,核心线程忙不过来时任务就在这排队。
  • threadFactory:线程工厂,用来创建线程。可以自定义线程名方便排查问题。比如给线程起名叫order-pool-thread-1。
  • handler:拒绝策略,队列满了,线程也到顶了,新任务怎么处理。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐