面试官不按套路,竟然问我Java线程池是怎么统计线程空闲时间?
背景介绍:
你刚从学校毕业后,到新公司实习,试用期又被毕业,然后你又不得不出来面试,好在面试的时候碰到个美女面试官!
面试官: 小伙子,我看你简历上写的项目中用到了线程池,你知道线程池是怎样实现复用线程的?
这面试官是不是想坑我?是不是摆明了不让我通过?
难道你不应该问线程池有哪些核心参数?每个参数具体作用是什么?
往线程池中不断提交任务,线程池的处理流程是什么?
这些才是你应该问的,这些八股文我已经背熟了,你不问,瞎问什么复用线程?
幸亏我看了一灯的八股文,听我给你背一遍!
我: 线程池复用线程的逻辑很简单,就是在线程启动后,通过while死循环,不断从阻塞队列中拉取任务,从而达到了复用线程的目的。
具体源码如下:
// 线程执行入口
public void run() {
runWorker(this);
}
// 线程运行核心方法
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock();
boolean completedAbruptly = true;
try {
// 1. 使用while死循环,不断从阻塞队列中拉取任务
while (task != null || (task = getTask()) != null) {
// 加锁,保证thread不被其他线程中断(除非线程池被中断)
w.lock();
// 2. 校验线程池状态,是否需要中断当前线程
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
// 3. 执行run方法
task.run();
} catch (RuntimeException x) {
thrown = x;
throw x;
} catch (Error x) {
thrown = x;
throw x;
} catch (Throwable x) {
thrown = x;
throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
runWorker方法逻辑很简单,就是不断从阻塞队列中拉取任务并执行。
面试官: 小伙子,有点东西。我们都知道线程池会回收超过空闲时间的线程,那么线程池是怎么统计线程的空闲时间的?
美女面试官的问题真刁钻,让人头疼啊!这问的也太深了吧!
没看过源码的话,真不好回答。
我: 嗯...,可能是有个监控线程在后台不停的统计每个线程的空闲时间,看到线程的空闲时间超过阈值的时候,就回收掉。
面试官: 小伙子,你的想法挺不错,逻辑很严谨,你确定线程池内部是这么实现的吗?
问得我有点不自信了,没看过源码不能瞎蒙。
我还是去瞅一眼一灯写的八股文吧。
我: 这个我知道,线程池统计线程的空闲时间的实现逻辑很简单。
阻塞队列(BlockingQueue)提供了一个poll(time, unit)
方法用来拉取数据,
作用就是: 当队列为空时,会阻塞指定时间,然后返回null。
线程池就是就是利用阻塞队列的这个方法,如果在指定时间内拉取不到任务,就表示该线程的存活时间已经超过阈值了,就要被回收了。
具体源码如下:
// 从阻塞队列中拉取任务
private Runnable getTask() {
boolean timedOut = false;
for (; ; ) {
int c = ctl.get();
int rs = runStateOf(c);
// 1. 如果线程池已经停了,或者阻塞队列是空,就回收当前线程
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// 2. 再次判断是否需要回收线程
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 3. 在指定时间内,从阻塞队列中拉取任务
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
// 4. 如果没有拉取到任务,就标识该线程已超时,然后就被回收
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
面试官: 小伙子,可以啊,你是懂线程池源码的。再问你个问题,如果线程池抛异常了,也没有try/catch,会发生什么?
美女面试官你这是准备打破砂锅问到底,铁了心不让我过,是吧?
我的代码风格是很严谨的,谁写的业务代码不try/catch,也没遇到过这种情况。
让我再看一下一灯总结的八股文吧。
我: 有了,线程池中的代码如果抛异常了,也没有try/catch,会从线程池中删除这个异常线程,并创建一个新线程。
不信的话,我们可以测试验证一下:
/**
* @author 一灯架构
* @apiNote 线程池示例
**/
public class ThreadPoolDemo {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
// 1. 创建一个单个线程的线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
// 2. 往线程池中提交3个任务
for (int i = 0; i < 3; i++) {
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 关注公众号:一灯架构");
throw new RuntimeException("抛异常了!");
});
}
// 3. 关闭线程池
executorService.shutdown();
}
}
输出结果:
pool-1-thread-1 关注公众号:一灯架构
pool-1-thread-2 关注公众号:一灯架构
pool-1-thread-3 关注公众号:一灯架构
Exception in thread "pool-1-thread-1" java.lang.RuntimeException: 抛异常了!
at com.yideng.SynchronousQueueDemo.lambda$main$0(ThreadPoolDemo.java:21)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Exception in thread "pool-1-thread-2" java.lang.RuntimeException: 抛异常了!
at com.yideng.SynchronousQueueDemo.lambda$main$0(ThreadPoolDemo.java:21)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Exception in thread "pool-1-thread-3" java.lang.RuntimeException: 抛异常了!
at com.yideng.SynchronousQueueDemo.lambda$main$0(ThreadPoolDemo.java:21)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
从输出结果中可以看出,线程名称并不是同一个,而是累加的,说明原线程已经被回收,新建了个线程。
我们再看一下源码,验证一下:
// 线程抛异常后,退出逻辑
private void processWorkerExit(ThreadPoolExecutor.Worker w, boolean completedAbruptly) {
if (completedAbruptly)
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
// 1. 从工作线程中删除当前线程
workers.remove(w);
} finally {
mainLock.unlock();
}
// 2. 中断当前线程
tryTerminate();
int c = ctl.get();
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && !workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
// 3. 新建一个线程
addWorker(null, false);
}
}
如果想统一处理异常,可以自定义线程创建工厂,在工厂里面设置异常处理逻辑。
/**
* @author 一灯架构
* @apiNote 线程池示例
**/
public class ThreadPoolDemo {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
// 1. 创建一个单个线程的线程池
ExecutorService executorService = Executors.newSingleThreadExecutor(runnable -> {
// 2. 自定义线程创建工厂,并设置异常处理逻辑
Thread thread = new Thread(runnable);
thread.setUncaughtExceptionHandler((t, e) -> {
System.out.println("捕获到异常:" + e.getMessage());
});
return thread;
});
// 3. 往线程池中提交3个任务
for (int i = 0; i < 3; i++) {
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 关注公众号:一灯架构");
throw new RuntimeException("抛异常了!");
});
}
// 4. 关闭线程池
executorService.shutdown();
}
}
输出结果:
Thread-0 关注公众号:一灯架构
捕获到异常:抛异常了!
Thread-1 关注公众号:一灯架构
捕获到异常:抛异常了!
Thread-2 关注公众号:一灯架构
捕获到异常:抛异常了!
面试官: 小伙子,论源码,还是得看你,还是你背的熟。现在我就给你发offer,薪资直接涨10%,明天9点就来上班吧,咱们公司实行996工作制。
我是「一灯架构」,如果本文对你有帮助,欢迎各位小伙伴点赞、评论和关注,感谢各位老铁,我们下期见
面试官不按套路,竟然问我Java线程池是怎么统计线程空闲时间?的更多相关文章
- 面试官,不要再问我“Java GC垃圾回收机制”了
Java GC垃圾回收几乎是面试必问的JVM问题之一,本篇文章带领大家了解Java GC的底层原理,图文并茂,突破学习及面试瓶颈. 楔子-JVM内存结构补充 在上篇<JVM之内存结构详解> ...
- 面试官,不要再问我“Java 垃圾收集器”了
如果Java虚拟机中标记清除算法.标记整理算法.复制算法.分代算法这些属于GC收集算法中的方法论,那么"GC收集器"则是这些方法论的具体实现. 在面试过程中这个深度的问题涉及的比较 ...
- 面试官,不要再问我“Java虚拟机类加载机制”了
关于Java虚拟机类加载机制往往有两方面的面试题:根据程序判断输出结果和讲讲虚拟机类加载机制的流程.其实这两类题本质上都是考察面试者对Java虚拟机类加载机制的了解. 面试题试水 现在有这样一道判断程 ...
- 面试官,不要再问我“Java虚拟机类加载机制”了(转载)
关于Java虚拟机类加载机制往往有两方面的 面试题:根据程序判断输出结果和讲讲虚拟机类加载机制的流程.其实这两类题本质上都是考察面试者对Java虚拟机类加载机制的了解. 面试题试水 现在有这样一道判断 ...
- 面试官,不要再问我“Java 垃圾收集器”了(转载)
如果Java虚拟机中标记清除算法.标记整理算法.复制算法.分代算法这些属于GC收集算法中的方法论,那么"GC收集器"则是这些方法论的具体实现. 在 面试过程中这个深度的问题涉及的比 ...
- java多线程系类:JUC线程池:05之线程池原理(四)(转)
概要 本章介绍线程池的拒绝策略.内容包括:拒绝策略介绍拒绝策略对比和示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3512947.html 拒绝策略 ...
- java多线程系类:JUC线程池:03之线程池原理(二)(转)
概要 在前面一章"Java多线程系列--"JUC线程池"02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包 ...
- java多线程系类:JUC线程池:02之线程池原理(一)
在上一章"Java多线程系列--"JUC线程池"01之 线程池架构"中,我们了解了线程池的架构.线程池的实现类是ThreadPoolExecutor类.本章,我 ...
- java多线程、线程池及Spring配置线程池详解
1.java中为什么要使用多线程使用多线程,可以把一些大任务分解成多个小任务来执行,多个小任务之间互不影像,同时进行,这样,充分利用了cpu资源.2.java中简单的实现多线程的方式 继承Thread ...
- java线程池及创建多少线程合适
java线程池 1.以下是ThreadPoolExecutor参数完备构造方法: public ThreadPoolExecutor(int corePoolSize,int maximumPoolS ...
随机推荐
- Hive 组件安装配置
下载和解压安装文件 基础环境和安装准备 Hive组件的部署规划和软件包路径如下: (1)当前环境中已安装 Hadoop全分布系统 (2)本地安装 MySQL数据库(账号 root,密码 Passwor ...
- C#实现HTTP访问类HttpHelper
在项目开发过程中,我们经常会访问第三方接口,如我们需要接入的第三方接口是Web API,这时候我们就需要使用HttpHelper调用远程接口了.示例中的HttpHelper类使用Log4Net记录了每 ...
- JAVA反序列化漏洞修复解决方法
MyObject类建立了Serializable模块,而且重新写过了readObject()变量,仅有建立了Serializable模块的类的目标才能够被实例化,沒有建立此模块的类将无法使他们的任意状 ...
- mysql_varchar与中英文关系总结
mysql 4.0版本以下,varchar(50), 指的是50字节,如果存放utf8汉字时,只能存放16个(每个汉字3字节) mysql 5.0版本以上,varchar(50), 指的是50字符,无 ...
- 一文搞懂mysql索引底层逻辑,干货满满!
一.什么是索引 在mysql中,索引是一种特殊的数据库结构,由数据表中的一列或多列组合而成,可以用来快速查询数据表中有某一特定值的记录.通过索引,查询数据时不用读完记录的所有信息,而只是查询索引列即可 ...
- Hybrid app本地开发如何调用JSBridge
前天同事问我公司内部的小程序怎么对接的,我回忆了一下,简单记录了一下前端同学需要注意的点. 背后还有小程序架构.网络策略等等.当时恰逢小程序架构调整,(老架构的时候我就发现了有一个问题点可以优化,但是 ...
- e1000e网卡驱动在麒麟3.2.5上编译安装
一.清空原驱动 因为系统安装完毕后系统中自带了e1000e的网卡驱动,会影响后面自行编译的驱动 所以先用find命令找出并删除掉所有关于e1000e的驱动文件 find / -name "* ...
- [AI]-模型测试和评价指标
模型测试 import cv2 from torchvision import transforms, datasets, models from torch.utils.data import Da ...
- Windows DNS服务器的子网掩码排序
对于跨多个站点部署的应用服务,会在各个站点都有服务器,并且对应不同的IP地址.我们希望每个客户端访问就近的资源.这个时候可以启用Windows DNS服务器中的子网掩码排序(子网优先)netmask ...
- Redis 的大 Key 对持久化有什么影响?
作者:小林coding 图解计算机基础(操作系统.计算机网络.计算机组成.数据库等)网站:https://xiaolincoding.com 大家好,我是小林. 上周有位读者字节一二面时,被问到:Re ...