10、线程池
大约 5 分钟
1.什么是线程池/优点
线程池就是利用池化技术来管理线程。使用线程池的优点:
- 降低资源消耗。线程池可以规定线程数量,并且可以复用线程。
- 提高响应速度。因为线程池当中的线程是可以复用的,所以可以免去线程创建和销毁所带来的时间消耗
- 方便进行线程管理。线程池中的线程是可监控可管理的。
2.创建线程池的方式
- 使用工厂类Executors
- 使用ThreadPoolExecutor
⭐Executors
不推荐使用这个工厂类。因为它内部的很多线程池可能会导致OOM(比如FixedThreadPool、SingleThreadExecutor,使用无限队列,可能导致队列OOM。CachedThreadPool,使用无限线程,可能导致OOM),而且不方便进行灵活控制。
Executors中的线程池介绍:
- FixedThreadPool。它的核心线程数量和最大线程数量相同,阻塞队列使用LinkedBlockingQueue,即阻塞队列无限大,可能导致OOM
- SingleThreadExecutor。核心线程数和最大线程数都是1,阻塞队列同上,可能导致OOM
- CachedThreadPool。核心线程数为0,最大线程数为无限大,阻塞队列为SynchronousQueue同步队列,即阻塞队列不可存放作业,可能导致OOM
- ScheduledThreadPool。核心线程数自定,最大线程数为无限大,阻塞队列为DelayedWorkQueue延时队列(这个延时是指任务延时),可能导致OOM。
因为它们都会导致OOM,所以不推荐。
⭐ThreadPoolExecutor
推荐使用ThreadPoolExecutor来进行灵活控制线程池
2.1 构造参数
ThreadPoolExecutor的构造参数有哪些?
- 核心线程数:线程池的核心线程数,如果当前运行的线程数量小于这个数,那么会创建新线程来执行任务
- 最大线程数:线程池可以运行的最大线程数
- 空闲线程等待时间:如果线程空闲时间超过这个时间,那么会进行销毁
- 时间单位:上述的时间单位
- 线程池饱和策略:如果线程数量达到最大,任务队列也满了,那么会执行这个策略。
2.2 饱和策略
线程池饱和策略有哪些?
- 拒绝。直接抛出异常
- 调用者执行。通过调用者所在的线程来执行。这个会让调用者所在线程执行变慢,如果不在意可以考虑。因为这个不会抛弃任何任务。
- 丢弃。新的任务直接丢弃,不处理
- 丢弃最早。把消息队列中最早的任务丢掉,放入本个任务。
2.3 阻塞队列
- 阻塞队列有哪些?
- 有限队列。ArrayBlockingQueue。该队列有界限,底层数据结构是ArrayList。
- 无限队列。LinkedBlockingQueue。该队列默认长度为Inteter.MAX_VALUE(可手动指定有界),可以算作无界,底层用的是LinkedList。该队列用在了SingleThreadExecutor和FixedThreadPool中。其中SingleThreadExecutor的核心线程和最大线程都是1,FixedThreadPool的核心线程和最大线程是一致的。它们的任务队列可以看作无界的。
- 同步队列。同步队列的空间为0,不能存放任何任务。它用在CachedThreadPool中,CachedThreadPool的最大线程数为无限。
- 延迟队列。延迟队列底层使用堆来实现的。延迟队列排序方式不是任务的添加顺序,而是任务的延迟时间。每次出队的任务都是当前延迟最小的任务。
- LinkedBlockingQueue和ArrayBlockingQueue的区别?
- L默认是无界队列,默认长度是Integer.MAX_VALUE,而A在初始化的时候必须指定容量,是有界队列
- 锁机制不同,A没有进行锁分离,即入队和出队用的是一把ReentrantLock,而L是锁分离的,入队和出队用的是两把锁。也因此,L的并发性能会更好一些
- 底层实现不同,A底层是数组,L底层是链表
3. 线程池处理任务细节
线程池处理新来的任务有以下几个步骤:
- 如果当前线程池中运行的线程数量小于核心线程数,那么会创建新线程来执行
- 如果当前线程池中运行的线程数量大于等于核心线程数,但是任务队列还没有满,那么先放到任务队列中等待执行
- 如果任务队列也满了,那么会判断当前运行的线程数量是否大于最大线程,如果不大于,那么会继续创建线程来执行
- 如果当前运行的线程已经到达最大线程数量了,那么就拒绝调用饱和策略了。
4. 线程池是如何进行线程复用的
我们似乎并没有在上述线程池处理细节中发现如何进行线程复用的。实际上的奥秘在于Worker中。
我们知道,如果当前运行的线程数量不大于核心线程数,会addWorker。在大于核心线程数,会放到任务队列当中去,那么最终这个任务队列当中的任务是谁执行的呢?
没错,就是之前addWorker中的Worker。这些Worker不会在执行完一个任务后就释放掉,而是会阻塞等待任务队列中的任务,如果有任务来了,就会交给它们来执行。