JAVA面试系列:你了解线程池吗?

309 阅读3分钟

背景

面试官问这个,主要想考察 为什么需要线程池? 进一步为什么要用多线程 什么是线程和进程等等。要想学问大,就要多读、多抄、多写。

解答

线程和进程

名称描述
进程进程是资源分配的最小单位。每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。
线程线程是CPU调度的最小单位。同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。

线程池作用

  • 降低资源消耗

通过重复利用现有的线程来执行任务,避免多次创建和销毁线程。

  • 提高响应速度

因为省去了创建线程这个步骤,- 当任务到达时,任务可以不需要等到线程创建就能立即执行。

  • 提高线程的可管理性

线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。但是要做到合理的利用线程池,必须对其原理了如指掌。

线程池核心参数与参数协同工作

image.png

参数参数名称描述
corePoolSize线程池核心线程大小核心线程会一直存活,即使没有任务需要执行。当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理。设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
maximumPoolSize线程池最大线程数量当线程数>=corePoolSize且maximumPoolSize>corePoolSize,且任务队列已满时,线程池会创建新线程来处理任务。当线程数=maxPoolSize,且任务队列已满时,线程池会根据拒绝策略做相应的处理
keepAliveTime空闲线程存活时间一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定。
unit空闲线程存活时间单位keepAliveTime的计量单位
workQueue工作任务队列新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。阻塞队列,设计模式是生产者与消费者模式
threadFactory线程工厂创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等
handler拒绝策略当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略,就是解决这个问题的。

线程池拒绝策略:

  1. CallerRunsPolicy:该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
  2. AbortPolicy:该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。
  3. DiscardPolicy:该策略下,直接丢弃任务,什么都不做。
  4. DiscardOldestPolicy:该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列。

线程池生命周期

image.png

最佳实践

阿里开发手册建议: 【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

说明:Executors 返回的线程池对象的弊端如下:

  1. FixedThreadPoolSingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM
  2. CachedThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM