背景
面试官问这个,主要想考察 为什么需要线程池? 进一步为什么要用多线程 什么是线程和进程等等。要想学问大,就要多读、多抄、多写。
解答
线程和进程
| 名称 | 描述 |
|---|---|
| 进程 | 进程是资源分配的最小单位。每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。 |
| 线程 | 线程是CPU调度的最小单位。同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。 |
线程池作用
- 降低资源消耗
通过重复利用现有的线程来执行任务,避免多次创建和销毁线程。
- 提高响应速度
因为省去了创建线程这个步骤,- 当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性
线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。但是要做到合理的利用线程池,必须对其原理了如指掌。
线程池核心参数与参数协同工作
| 参数 | 参数名称 | 描述 |
|---|---|---|
| corePoolSize | 线程池核心线程大小 | 核心线程会一直存活,即使没有任务需要执行。当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理。设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭 |
| maximumPoolSize | 线程池最大线程数量 | 当线程数>=corePoolSize且maximumPoolSize>corePoolSize,且任务队列已满时,线程池会创建新线程来处理任务。当线程数=maxPoolSize,且任务队列已满时,线程池会根据拒绝策略做相应的处理 |
| keepAliveTime | 空闲线程存活时间 | 一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定。 |
| unit | 空闲线程存活时间单位 | keepAliveTime的计量单位 |
| workQueue | 工作任务队列 | 新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。阻塞队列,设计模式是生产者与消费者模式 |
| threadFactory | 线程工厂 | 创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等 |
| handler | 拒绝策略 | 当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略,就是解决这个问题的。 |
线程池拒绝策略:
- CallerRunsPolicy:该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
- AbortPolicy:该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。
- DiscardPolicy:该策略下,直接丢弃任务,什么都不做。
- DiscardOldestPolicy:该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列。
线程池生命周期
最佳实践
阿里开发手册建议: 【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
FixedThreadPool和SingleThreadPool: 允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。CachedThreadPool: 允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。