Java面试经常问到Java并发编程里的线程池,今天给大家总结线程池面试题及答案@mikechen
什么是线程池?为什么要用线程池?
线程池里面存放了若干数量的线程,这些线程给我们程序去使用,使用的时候,就去线程池里面取一个,用完了再还回来,而不再是自我销毁。
线程池带来的好处:
- 降低资源消耗
- 提高相应速度
- 提高线程的可管理型
线程池有哪7个核心参数?
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue < Runnable > workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
- corePoolSize:核心线程数。也就是最少的线程数,即使空闲也要维护的线程数。
- maximumPoolSize:池里最大线程数量。
- keepAliveTime:线程最大活跃时间
- unit:时间单位
- workQueue:阻塞队列。线程用完时,任务放进的队列。
- threadFactory:线程工厂
- handler:丢弃策略。当队列也满的时候,如何处理的策略。
讲讲线程池工作原理 ?
池化技术主要是为了减少线程创建的时间,将可以复用的线程放在池里随时使用。
1、线程池刚创建时候,没有线程。
2、线程池指定任务execute时:
【初始执行阶段】
- 如果线程池线程数量 < corePoolSize,会立刻创建线程并执行任务
- 如果线程池线程数量 > corePoolSize,会把任务放入阻塞队列中
【任务队列满】
- 如果阻塞队列任务已满,且正在运行的线程数< maximumPoolSize,继续创建非核心线程执行任务
- 如果阻塞队列任务已满,且正在运行的线程数 = maximumPoolSize,线程池会抛出RejectExecutionException由handler处理
【线程空闲】
- 线程如果空闲超过keepAliveTime,而且正在运行的线程数 > corePoolSize,该线程会被销毁。
如何设置合适的线程数量?
线程执行任务,主要是耗费的资源是CPU,所以:
- IO密集型:等待I/O的时间很长,线程通常处于等待状态,CPU没有被充分使用,可以多设置一点,通常设置为:2*CPU核心数+1
- CPU密集型:对CPU消耗大,线程太多的话线程切换反而浪费CPU时间片,通常设置为:CPU核心数+1
线程池拒绝策略有哪些?
- AbortPolicy:直接抛出异常,阻止线程正常运行
- CallerRunsPolicy:谁调用的execute,谁来执行(让父线程去执行)
- DiscardOldestPolicy:丢弃当前最老的任务,然后重新尝试执行execute
- DiscardPolicy:直接丢弃当前任务
谈谈任务队列?
任务队列只是先进先出队列吗? -> 如果任务有优先级怎么办? -> 知道优先级反转吗? -> 怎么解决优先级反转?
1、默认先进先出,可以用一些特殊的Queue,实现先进后出,例如LinkedBlockingDeque。
2、如果任务有优先级,更可以带权重的Queue,比如PriorityBlockingQueue。PriorityBlockingQueue是一个无界队列,利用小顶堆,把任务按照权重排序,出队一定是权重最大的。
3、优先级反转:线程A、B、C都需要执行任务,优先级A>B>C,其中A和C都去争抢一个资源,当低优先级线程C争取到了资源后,高优先级线程A即使拿到了CPU,也不能执行,从就绪状态变为等待资源的挂起状态。由于B处于就绪,A处于挂起(要等下一个调度才就绪),所以B明明优先级比A低,而且也没有资源竞争,却优先于A执行了。
优先级反转会导致任务执行顺序混乱。
4、解决方案:
核心就是,让C释放资源之前,B都不要去执行,那就提升C的优先级即可。
(1)最高优先级:低优先级任务C拿到资源时,将优先级提升至最大,释放后改回来。修改优先级会浪费CPU时间,因为不是每个资源都是多个线程抢的。
(2)优先级继承:高优先级任务A发现资源被低优先级任务C抢占,那么将低优先级任务C提升到和自己同等优先级。过程比较复杂,需要操作系统支持同等优先级。
(3)结合:高优先级任务A发现资源被低优先级任务C抢占,那么将低优先级任务C提升到最高优先级。
知道哪些常用的线程池?
1、ThreadpoolExecutor.newCachedThreadPool:线程池无限大,当执行上一个任务已经完成,会复用上一个任务的线程,而不是每次都新建线程。适合执行时间短的任务。
2、ThreadpoolExecutor.newSingleThreadExecutor:单线程运行的线程池。适合需要顺序执行的任务。
3、ThreadpoolExecutor.newFixedThreadPool:固定线程数量的线程池
4、ThreadpoolExecutor.newScheduleThreadPool:定时线程池。适合周期性执行的任务。
5、Executors.newWorkStealingPool:并发线程池(JDK 1.8)动态调整线程数,并且使用多个队列,以支持给定的并发级别。不保证任务执行顺序。
谈谈线程池里的线程?
线程是怎么回收空闲线程的?空闲的核心线程如何做到不被回收的?核心线程一定不能被回收吗?
1、不要回答有定时任务回收空闲线程,大No特No。
其实线程对象会被包装成worker,然后在循环遍历线程池任务队列时,超过指定时间获取不到任务,就开始移除worker,最后这些移除的worker会被GC从而回收线程。注意这里移除的是非核心线程。
2、有一个HashSet持有全部worker的引用,因此worker不会被回收,thread也就不会被回收。
3、当然不是,可以被回收,如果设置了allowCoreThreadTimeOut=true的话。
如果让你设计一个线程池,你如何设计?
至少需要4个结构:
1、线程池管理:Manger。处理线程生命周期,执行任务。
2、工作线程:Worker。执行具体的任务。
3、任务:Task。任务接口,供业务实现
4、任务队列:Queue。暂存没有资源执行的任务。
陈睿mikechen
10年+大厂架构经验,资深技术专家,就职于阿里巴巴、淘宝、百度等一线互联网大厂。
关注「mikechen」公众号,获取更多技术干货!
后台回复【面试】即可获取《史上最全阿里Java面试题总结》,后台回复【架构】,即可获取《阿里架构师进阶专题全部合集》