8.线程池
线程池简介
存放线程对象的容器;事先创建指定数量的线程对象,需要时从池中取,使用完后再放入池中,不需要手动销毁线程对象。线程池减少了创建和销毁线程所带来的开销。
线程池的优点:
- 降低资源的消耗:通过重复利用已经创建的线程,降低了创建和销毁线程所带来的开销。
- 提高响应速度:当任务到达时,可以不用等待线程的创建,就能立即执行。
- 提高线程的可管理性:线程池可观察/调节线程的数量,优化线程等。
简单线程池设计
首先要有个线程池,其功能有:
- 基础功能:开启/初始化/关闭。
- 对外提供获取线程池中线程的功能。
- 要具备把线程还到线程池的功能。
改成更利于用户使用的线程池:
需要考虑的问题:
- 初始创建多少个线程?
- 没有可用线程了怎么办?
- 缓冲数组要多长?
- 缓冲数组满了怎么办?
1.线程池的核心参数
- corePoolSize:核心线程数量
- maximumPoolSize:最大线程数量
- keepAliveTime:线程空闲后的存活时间
- unit:时间单位
- workQueue:用于存放任务的阻塞队列
- threadFactory:线程工厂类
- handler:当队列和最大线程池都满了之后的饱和策略
处理流程:
2.线程池可选择的阻塞队列
2.1.什么是阻塞队列?
支持阻塞插入和阻塞移除这两个附加操作的队列。
- 阻塞插入:当队列满时,队列会阻塞插入的线程,直到队列不满。
- 阻塞移除:当队列为空时,获取元素的线程会等待队列变为非空。
2.2.可选择的阻塞队列
- 有界队列:此队列有固定长度。
- 无界队列:就是此队列无限长,可一直往里追加元素。
- 同步移交队列:不存储元素的阻塞队列,每个插入的操作必须等到另一个线程去调用移除操作,才能成功。否则插入操作一直处于阻塞状态。
1.基于数组的有界阻塞队列:
1 |
|
2.基于链表的有界/无界阻塞队列
1 |
|
3.同步移阻塞队列
1 |
|
3.线程池可选择的饱和策略
当阻塞队列和最大线程都已经完了的时候,再有任务提交,就需要用到饱和策略。
一共有4中饱和策略:在ThreadPoolExecutor类的源码中能找到这些策略。
- AbortPolicy:终止策略(默认)
- DiscardPolicy:抛弃策略(抛弃新来的任务)
- DiscardOldestPolicy:抛弃旧任务策略(抛弃旧的任务)
- CallerRunsPolicy:调用者运行策略
测试4种饱和策略:
环境:
1 | // 任务类:要在线程池中执行的任务 |
3.1.AbortPolicy:终止策略(默认)
1 | // 饱和策略 |
结果:
1 | 线程[pool-1-thread-1]正在执行[线程任务1]任务 (任务1、任务2 是两个核心线程在执行) |
3.2.DiscardPolicy:抛弃策略
在PolicyTest类中添加如下方法,其余各处保持不变。
1 | /** |
结果:
1 | 线程[pool-1-thread-1]正在执行[线程任务1]任务 |
3.3.DiscardOldestPolicy:抛弃旧任务策略
在PolicyTest类中添加如下方法,其余各处保持不变。
1 | /** |
结果:会发现任务3、4被抛弃了。
1 | 线程[pool-1-thread-1]正在执行[线程任务1]任务 |
3.4.CallerRunsPolicy:调用者运行策略
在PolicyTest类中添加如下方法,其余各处保持不变。
1 | /** |
结果:
1 | 线程[main]正在执行[线程任务9]任务 |
4.线程池示意图
1.线程池的执行示意图:
主线程调用execute() 方法,执行一个线程任务:
- 如果核心线程池没满,会走①,立即创建一个线程,来执行这个任务。
- 如果核心线程池满了,会走②,将这个任务提交到阻塞队列里。核心线程会池里的线程会不断轮询阻塞队列,拿到新的任务来执行。
- 如果核心线程池和阻塞队列都满了,会走③,会创建新的线程(前提是线程池中的线程数,未超过最大线程数)
- 如果核心线程池、阻塞队列、最大线程池都满了,会走④,调用饱和策略。根据构造线程池时传入的不同的饱和策略,执行相应的处理。如果使用的饱和策略是CallerRunsPolicy,会接着走⑤,返回到主线程,通过主线程去运行任务的run方法来执行任务。
5.常用线程池(创建线程池的3种方式)
5.1.newCachedThreadPool
1 | /** |
5.2.newFixedThreadPool
1 | /** |
5.3.newSingleThreadExecutor
1 | /** |
5.4.newScheduledThreadPool
1 | /** |
6.向线程池提交任务两种方式
方式一:利用submit方法提交任务,并接收任务的返回结果。
1 |
|
方式二:利用execute方法提交任务,没有返回结果。
1 |
|