【后端面经-java】java线程池满的处理策略
1. 线程池介绍
1.1 基本作用
对多个线程使用的资源进行集中管理。
- 降低资源消耗:
- 复用线程,降低线程创建和销毁造成的消耗;
- 线程资源管理
- 提高管理效率;
- 提高线程的响应速度
- 在线程池中随时等待被执行,CPU不用等到线程创建时间;
1.2 处理流程
当一个线程进入线程池之后,会进行如下的处理步骤:
-
首先查看核心线程池是否满
- 如果没满,线程将在此处等待被调度执行;
-
如果核心线程池满了,那么查看队列是否满了
- 如果没满,线程在这里等待进入核心线程池;
-
如果队列也满了,那么查看临时线程池是否满了
- 如果没满,创建临时线程来处理任务。
-
如果临时线程池也满了,那就要根据
2.线程池满的处理策略
进行线程处理。
如下图所示:
当调度者需要调度一个线程的时候,按照如下步骤:
- 从核心线程池中获取一个线程,执行任务;
- 如果线程处于等待态,获取下一个线程继续执行;
- 某一个任务执行完毕后,线程返回
就绪态
而不是终止态
,放入线程池中复用。
1.3 线程池大小设置
-
CPU操作密集的任务
- 由于线程操作多半需要占据CPU资源,因此一个线程运行的过程中基本上很少会出现
某一线程进入等待态而调度下一个线程
的情况; - 因此CPU调度线程的速度偏慢,因此线程池大小不应过大,一般为
CPU核心数+1
;这样可以保证CPU的效率最高; - 如果线程池容量过大,那么不仅对CPU运行是一个很大的负担,而且大量线程都处于等待运行的阶段,等待时间过长,可能出现响应过慢的情况。
- 由于线程操作多半需要占据CPU资源,因此一个线程运行的过程中基本上很少会出现
-
I/O操作密集的任务
- 对于I/O操作密集的任务,线程对于CPU的资源占用常常被I/O等操作打断,此时线程进入等待态,CPU继续调度下一个线程;
- CPU调度线程的速度偏快,线程池大小可以尽量大一点,这样能够保障CPU资源的利用率,提高线程执行效率;
- 如果线程池容量过小,CPU在调度一段时间之后,所有线程都进入等待态,此时就会出现CPU
空等
的情况,不利于资源有效利用。
-
注意
-
对于
就绪态
、等待态
等线程状态和生命周期的介绍,可参考这篇博客
1.4 线程池参数
线程池的构造方法如下所示:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler);
线程池的创建包含以下参数:
- corePoolSize:核心线程池容量大小
- 如前文所述,线程进行核心线程池即可等待调度执行
- maximumPoolSize:最大线程池大小
- 通过这个来判断线程池是否已满。
MaximumPoolSize = CorePoolSize + WorkQueue + 临时线程池大小
- 通过这个来判断线程池是否已满。
- workQueue:任务队列
- 无法进入核心线程池的线程将进入任务队列等待进入池中;
- 阻塞队列对象,一般需要设定容量大小
- keepAliveTime:线程存活时间
- 线程池已满的情况下,空闲多余的线程有个存活时间,超过这个时间还没有进入核心线程池,那么将被丢弃;
- timeUnit:线程存活时间单位
- 配合线程存活时间使用;
- handler:拒绝策略
- 当前线程池满了之后(超过maxmumPoolSize),对于新的线程的处理策略,
- 包括四种,在
2.1 默认--拒绝策略handler
有详细论述
- threadFactory:线程工厂
- 用于创建线程池中的线程。
2. 线程池满的处理策略
2.1 默认--拒绝策略handler
线程池满了之后,一般的处理方式是丢弃某一线程,并且抛出异常。
Handler有四种策略:
- AbortPolicy:直接抛出异常
RejectedExecutionException
。 - DiscardPolicy:直接丢弃任务,但是不抛出异常( 默认)。
- DiscardOldestPolicy:丢弃队列中最旧的任务,然后重新尝试执行任务;
- CallerRunsPolicy:由调用线程处理该任务。