金三银四跳槽季即将来临,想必有些猿友已经蠢蠢欲动在做相关的准备了!在接下来的日子里,笔者将坚持写作、分享Java工程师在面试求职期间的方方面面,包括简历制作、面试场景复现、面试题解答、谈薪技巧 以及 项目的实战!今天我们来聊聊Java中线程池相关的知识!
在上一篇文章中我们模拟了一个面试场景,灵魂式拷问了Java中Synchronized相关的知识,可以点击链接查看详情:Java面试系列之并发编程专题-Synchronized灵魂拷问
下面我们开撸!值得一提的是,以下内容来自程序员实战基地fightjava.com一位网友最近的面试场景,笔者尝试着将其复现,其中有些是笔者自行整理补充的,若有不对的地方还请多多指正!!
1. 面试官:Java线程池底层是怎么实现的?大概说下
(1)画外音:TND的,一上来就问底层实现原理……真应了那句“面试造火箭,工作拧螺丝”,没办法只能硬着头皮上,既然说“大概说下”,那大概能回答 “工作线程队列”和“任务队列”应该就阔以了!
(2)回答:在Java中,所谓的线程池中的“线程”,其实是被抽象为一个静态内部类Worker,即“工作线程”,它基于AQS(抽象队列同步器)实现、存放在线程池一个成员变量中,其名为:“工作线程队列” HashSet<Worker> workers,而将等待被执行的任务存放在成员变量 “任务队列” workQueue(BlockingQueue<Runnable> workQueue)中;
这样一来,整个线程池实现的基本思想大概就是:从任务队列workQueue中不断取出需要执行的任务,放在工作线程队列Workers中进行处理;
2.面试官:嗯,不错,说一说创建线程池的几个核心构造参数?
(1)画外音:这个倒不难,撸过ThreadPoolExecutor的估计都晓得!
(2)回答: Java中创建线程池其实非常灵活,我们可以通过配置不同的参数,创建出行为不同的线程池,这几个参数包括:
A. corePoolSize:线程池的核心线程数;
B. maximumPoolSize:线程池允许的最大线程数;
C. keepAliveTime:超过核心线程数时闲置线程的存活时间;
D. workQueue:任务执行前保存任务的队列,保存着execute方法待提交的Runnable任务;
附上代码:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler);
3.面试官:那线程池中的线程是怎么创建的?是一开始就随着线程池的启动就创建好的吗?
(1)画外音:这还是挺考验底层源码阅读能力的,看过ThreadPoolExecutor的创建、executor下的执行方法API 即execute()方法的应该可以回答上!
(2)回答:不是;线程池在创建后执行初始化策略时默认是不启动工作线程Worker的,而是等待有请求到来时才启动,每当我们调用execute()方法添加一个任务时,线程池会做如下判断:
A.如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列workQueue;
如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
如果队列满了,而且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会抛出一个拒绝执行的异常RejectExecutionException;只有当一个线程完成任务时,它会从队列中取下一个任务来执行;
而当一个线程无事可做(也就是空闲) 且 超过一定的时间(keepAliveTime)时,线程池会判断如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉(销毁线程回收资源的过程),所以当线程池的所有任务完成后,它最终会收缩到corePoolSize的大小;
4.面试官:你刚刚提到可以通过配置不同的参数创建出不同的线程池,那么Java中默认实现好的线程池又有哪些呢?请比较它们的异同?
(1)画外音:这其实就是考察Executors下几种常见的线程池了,下面的回答有点官方哈!
(2)回答:
A.SingleThreadExecutor线程池:这种线程池只有一个核心线程在工作,也就是相当于单线程串行执行所有任务;如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它,此线程池保证所有任务的执行顺序按照任务的提交顺序执行;其中涉及到的参数含义为:
Executors.newSingleThreadExecutor();
corePoolSize:1,只有一个核心线程在工作;
maximumPoolSize:1;
keepAliveTime:0L;
workQueue:newLinkedBlockingQueue<Runnable>(),其缓冲队列是*的;
B.FixedThreadPool线程池:这种线程池是固定大小的线程池,只有核心线程;每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小;线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程;FixedThreadPool多数是针对一些很稳定很固定的正规并发线程;
更多请见:http://www.mark-to-win.com/tutorial/51076.html