Java线程池面试题
为什么要用线程池
- 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗
- 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行
- 方便管理线程:线程是稀缺资源,如果无条件地创建,不仅会消耗资源,还会降低线程的稳定性,使用线程池可以统一分配、调优和监考。
线程池的核心参数
- corePoolSize:核心线程的数量
- maximumPoolSize:线程池能创建的最大线程个数
- keepAliveTime:空闲线程存活时间
- unit:时间单位
- workQueue:用于保存任务的阻塞队列
- threadFactory:创建线程的工程类
- hadler:饱和策略
常见线程池的区别以及特点
CachedThreadPool:
- 特点:newCachedThreadPool创建一个可缓存的线程池,如果当前线程池的长度超过了处理的需要,它可以灵活的回收空闲的线程,当需要添加的时候可以灵活的添加
- 缺点:maximumPoolSize被设置为Inter.MAX_VALUE,可能会造成OOM
FixedThreadPool:
- 特点:创建一个定长的线程池,可控制线程最大并发数,超出的任务会在线程中等待。
- 缺点:线程数量是固定的,但是阻塞队列是LinkedBlockingQueue,是无界队列,也可能会造成OOM
ScheduledThreadPool:
- 特点:创建一个固定长度的线程,而且支持定时的以及周期性的任务执行,类似Timer
- 缺点:底层封装了PriorityQueue,同样是无界队列,可能会造成OOM
SingleThreadExecutor:
- 特点:单线程化的线程池,它会用唯一的工作线程来执行任务。如果这个线程因为异常结束,那么会有一个新的线程来替代它。它必须保证前一项任务完成才能执行后一项。
- 缺点:因为是单线程,高并发下有压力
为什么我们不用Executors默认创建线程池的方法,而直接自己手动去调用ThreadPoolExecutor去创建线程池
Executors 返回的线程池对象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2)CachedThreadPool和 ScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
线程池的饱和策略有哪些
- ThreadPoolExecutor.AbortPolicy:抛出RejectedExecutionException来拒绝任务的处理
- ThreadPoolExecutor.CallerRunsPolicy:调用提交任务的线程运行任务(比如A提交线程,A运行任务)。但是会降低新任务提交速度,影响程序的整体性能。
- ThreadPoolExecutor.DiscardPolicy:不处理新任务,直接丢弃掉
- ThreadPoolExecutor.DiscardOldestPolicy:丢弃掉最早的未处理的任务
线程池执行原理
- 判断线程池的核心线程数是不是已满,如果不是则创建一个新的工作线程来执行任务。
- 如果核心线程数已满,则将提交的任务放在保存任务的阻塞队列中。
- 如果工作任务队列满了,则创建一个新的线程来执行任务,直到数量到达maximumPoolSize
- 最后如果达到线程池最大线程数,则采取对应的饱和策略
线程池中submit()和 execute()方法有什么区别
相同点:
- 都可以提交任务到线程池中
不同点
- 接受参数:submit只能执行Runnable类型的任务,submit可以执行Runnable和Callable类型的任务
- 返回值:submit方法可以返回持有计算结果的Future对象,而execute没有
- 异常处理:submit可以方便处理异常
Java中Executor、Executors和ExecuteService的区别
- Executor是最基本的接口,只定义了一个execute方法
- ExecuteService是一个高级的接口,实现了Executor并进行了扩展,比如实现了submit方法。这个接口的目的是方便我们使用底层不同的线程池,类似List接口,屏蔽底层差异。
- Executors是一个工具类,使用这个工具类可以方便的创建线程。让我们可以不用手动地指定线程池的各个参数,比如Executors.newFixedThreadPool(10);
线程池有哪些状态
- Running:正常状态,可以接受其他线程
- Shutdown:不接受新的任务提交,但是会继续处理等待队列中的任务
- Stop:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程
- Tidying:所有的任务都销毁,workerCount(线程数量)为0,线程池在向Tidying状态转换时,会执行钩子方法terminated()
- Terminated:terminated()方法介绍后,就会变成这个
如何合理分配线程池大小
- CPU密集型,任务可以少配置数,大概和CPU核数相当,这样可以使得每个线程在执行任务
- IO密集型,大部分线程在阻塞,故需要多配置线程数,2 * cpu核数
线程池如何实现动态修改
线程池提供了部分setter方法可以设置线程池的参数:
- 修改线程数,最大线程数,空闲线程停留时间,拒绝策略等
- 可以将线程池的配置参数放入配置中心,然后直接在配置中心修改
什么时候需要修改?
- 需要监考报警策略,获取线程池状态指标,当指标判定为异常后再报警
- 分析指标原因,评估策略,然后通过上述线程池提供的接口进行修改