【面试常考】
之前我们讲到的多线程案例里面的Executors 本质上是 ThreadPoolExecutor 类的封装.
ThreadPoolExecutor 提供了更多的可选参数, 可以进一步细化线程池行为的设定.
什么是ThreadPoolExcutor类?
首先通过文档我们看看ThreadPoolExcutor提供给我们的构造方法
它提供了很多种构造方法,可以支持很多参数,支持很多选项,让我们创建出不同风格的线程池。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
我们重点关注这个构造方法解释一下参数的含义:
- corePoolSize:核心线程数 (类似于正式员工)
- maximumPoolSize 最大线程数 (类似于正式员工+实习员工)
需要注意的是:核心线程数即使线程空闲也不会被销毁,但是最大线程数中除去核心线程数部分如果空闲太长时间则会被销毁,防止占用系统资源
- keepAliveTime 允许“实习员工”(最大线程数 中除去核心线程数)空闲的最大时间
- unit时间的单位
- workQueue 任务队列 【使用者不传入,线程池就会自己内部创建一个】
- threadFactory 描述线程是如何创建的 【上个文章提到的工厂对象,程序员可以手动指定线程的创建策略】
- RejectedExecutionHandler handler 拒绝策略 【实现“高并发’服务器非常有意义】
【面试考频:☆☆☆☆☆】线程池的拒绝策略
什么是拒绝策略?
线程池的拒绝策略~线程池的任务队列已经满(工作线程已经忙不过来了)又有别人往里添加新的任务线程池应对方法。
标准库里提供的拒绝策略:
中止策略ThreadPoolExecutor.AbortPolicy
【丢弃任务并抛出RejectedExecutionException异常。】
不处理新添加的任务,并且当前正在处理的所有任务全部中断;
线程池默认的拒绝策略,在任务不能再提交的时候,抛出异常,及时反馈程序运行状态
ThreadPoolExecutor.CallerRunsPolicy
【由调用线程处理该任务】
提交的任务被拒绝了,则由调用线程(提交任务的线程)直接执行此任务
忽略最先加入队列的任务ThreadPoolExecutor.DiscardOldestPolicy
【丢弃队列最前面的任务,然后重新提交被拒绝的任务】
是一种喜新厌旧的拒绝策略。是否要采用此种拒绝策略,还得根据实际业务是否允许丢弃老任务来认真衡量。
忽略此任务 ThreadPoolExecutor.DiscardPolicy
【丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃】
使用此策略,可能会使我们无法发现系统的异常状态。
线程池执行流程
文字版:
- 当新加入一个任务时,先判断当前线程数是否大于核心线程数,如果不大于,则新建线程并执行任务;
- 如果大于,则判断任务队列是否已满,如果没满,则把任务添加到任务队列中等待线程执行
- 如果任务队列满了,则判断当前线程数量是否超过最大线程数?如果不超过,则新建线程执行此任务
- 如果结超过了,执行拒绝策略。
【面试问题2】线程池的线程数目如何确定?
线程池不是可以自定义线程数目,那么在实际开发中,线程池的线程数目, 如何确定? 设定成几比较合适?
考察点:是如何设置线程池数目的方法(实验+压测)
实验验证法:
针对程序进行性能测试,分别给线程池设置成不同的数目。 N 1.5N 2N 0.5N都测试;
分别记录每种情况下程序的核心性能指标和系统负载情况,再进行选择。
回顾练习:
使用ThreadPoolExecutor创建一个忽略最新任务的线程池,创建规则:
1.核心线程数为5
2.最大线程数为10
3.任务队列为100
4.拒绝策略为忽略最新任务