adguard没有核心 core no_面试官:线程池如何按照core、max、queue的执行顺序去执行?...

前言

这是一个真实的面试题。

前几天一个朋友在群里分享了他刚刚面试候选者时问的问题:"线程池如何按照core、max、queue的执行循序去执行?"

我们都知道线程池中代码执行顺序是:corePool->workQueue->maxPool,源码我都看过,你现在问题让我改源码??

一时间群里炸开了锅,小伙伴们纷纷打听他所在的公司,然后拉黑避坑。(手动狗头,大家一起调侃٩(๑❛ᴗ❛๑)۶)

关于线程池他一共问了这么几个问题:

  • 线程池如何按照core、max、queue的顺序去执行?
  • 子线程抛出的异常,主线程能感知到么?
  • 线程池发生了异常该怎样处理?

全是一些有意思的问题,我之前也写过一篇很详细的图文教程:【万字图文-原创】 | 学会Java中的线程池,这一篇也许就够了! ,不了解的小伙伴可以再回顾下~

但是针对这几个问题,可能大家一时间也有点懵。今天的文章我们以源码为基础来分析下该如何回答这三个问题。(之前没阅读过源码也没关系,所有的分析都会贴出源码及图解)

线程池如何按照core、max、queue的顺序执行?

问题思考

对于这个问题,很多小伙伴肯定会疑惑:"别人源码中写好的执行流程你为啥要改?这面试官脑子有病吧……"

这里来思考一下现实工作场景中是否有这种需求?之前也看到过一份简历也写到过这个问题:

f98182fafa7d5564e8f9a4e5cb021d4b.png

场景描述.png

一个线程池执行的任务属于IO密集型,CPU大多属于闲置状态,系统资源未充分利用。如果一瞬间来了大量请求,如果线程池数量大于coreSize时,多余的请求都会放入到等待队列中。等待着corePool中的线程执行完成后再来执行等待队列中的任务。

试想一下,这种场景我们该如何优化?

我们可以修改线程池的执行顺序为corePool->maxPool->workQueue。 这样就能够充分利用CPU资源,提交的任务会被优先执行。当线程池中线程数量大于maxSize时才会将任务放入等待队列中。

你就说巧不巧?面试官的这个问题显然是经过认真思考来提问的,这是一个很有意思的问题,下面就一起看看如何解决吧。

线程池运行流程

我们都知道线程池执行流程是先corePool再workQueue,最后才是maxPool的一个执行流程。

51c345f985e47d4c4589dcffabf780e0.png

执行流程.png

线程池核心参数

在回顾下ThreadPoolExecutor.execute()源码前我们先回顾下线程池中的几个重要参数:

b42f66a89677899f670844f5d9eadecc.png

线程池核心参数.png

我们来看下这几个参数的定义:corePoolSize: 线程池中核心线程数量maximumPoolSize: 线程池中最大线程数量keepAliveTime: 非核心的空闲线程等待新任务的时间 unit: 时间单位。配合allowCoreThreadTimeOut也会清理核心线程池中的线程。workQueue: 基于Blocking的任务队列,最好选用有界队列,指定队列长度threadFactory: 线程工厂,最好自定义线程工厂,可以自定义每个线程的名称handler: 拒绝策略,默认是AbortPolicy

ThreadPoolExecutor.execute()源码分析

我们可以看下execute()如下:

253c27cbb516532e7c6c61041f5bee5d.png

execute执行源码.png

接着来分析下执行过程:

  1. 第一步:workerCountOf(c)时间计算当前线程池中线程的个数,当线程个数小于核心线程数
  2. 第二步:线程池线程数量大于核心线程数,此时提交的任务会放入workQueue中,使用offer()进行操作
  3. 第三步:workQueue.offer()执行失败,新提交的任务会直接执行,addWorker()会判断如果当前线程池数量大于最大线程数,则执行拒绝策略

好了,到了这里我们都已经很清楚了,关键在于第二步和第三步如何交换顺序执行呢?

解决思路

仔细想一想,如果修改workQueue.offer()的实现不就可以达到目的了?我们先来画图来看一下:

491a57b4b028f3206896188a4b7cd411.png

问题思路.png

现在的问题就在于,如果当前线程池中coreSize < workCount < maxSize时,一定会先执行offer()操作。

我们如果修改offer的实现是否可以完成执行顺序的更换呢?这里也是画图来展示一下:

0488c75551bad432c7ee7ca3750cad00.png

解决方式.png

Dubbo中EagerThreadPool解决方案

凑巧Dubbo中也有类似的实现,在Dubbo的EagerThreadPool自定义了一个BlockingQueue,在offer()方法中,如果当前线程池数量小于最大线程池时,直接返回false,这里就达到了调节线程池执行顺序的目的。

43fe79502bfe535612300ad2353feb27.png

dubbo中解决方案.png

源码直达:https://github.com/apache/dubbo/blob/master/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/eager/TaskQueue.java

看到这里一切都真相大白了,解决思路以及方案都很简单,学会了没有?

这个问题背后还隐藏了一些场景的优化、源码的扩展等等知识,果然是一个值得思考的好问题。

子线程抛出的异常,主线程能感知到么?

问题思考

这个问题其实也很容易回答,也仅仅是一个面试题而已,实际工作中子线程的异常不应该由主线程来捕获。

针对这个问题,希望大家清楚的是: 我们要明确线程代码的边界,异步化过程中,子线程抛出的异常应该由子线程自己去处理,而不是需要主线程感知来协助处理。

解决方案

解决方案很简单,在虚拟机中,当一个线程如果没有显式处理异常而抛出时会将该异常事件报告给该线程对象的 java.lang.Thread.UncaughtExceptionHandler 进行处理,如果线程没有设置 UncaughtExceptionHandler,则默认会把异常栈信息输出到终端而使程序直接崩溃。

所以如果我们想在线程意外崩溃时做一些处理就可以通过实现 UncaughtExceptionHandler 来满足需求。

我们使用线程池设置ThreadFactory时可以指定UncaughtExceptionHandler,这样就可以捕获到子线程抛出的异常了。

代码示例

具体代码如下:

/** * 测试子线程异常问题 * * @author wangmeng * @date 2020/6/13 18:08 */public class ThreadPoolExceptionTest {    public static void main(String[] args) throws InterruptedException {        MyHandler myHandler = new MyHandler();        ExecutorService execute = new ThreadPoolExecutor(10, 10,                0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), new ThreadFactoryBuilder().setUncaughtExceptionHandler(myHandler).build());        TimeUnit.SECONDS.sleep(5);        for (int i = 0; i 

执行结果:

6d0577564e9a4d14f056d25013ce8508.png

执行结果.png

UncaughtExceptionHandler 解析

我们来看下Thread中的内部接口UncaughtExceptionHandler:

public class Thread {    ......    /**     * 当一个线程因未捕获的异常而即将终止时虚拟机将使用 Thread.getUncaughtExceptionHandler()     * 获取已经设置的 UncaughtExceptionHandler 实例,并通过调用其 uncaughtException(...) 方     * 法而传递相关异常信息。     * 如果一个线程没有明确设置其 UncaughtExceptionHandler,则将其 ThreadGroup 对象作为其     * handler,如果 ThreadGroup 对象对异常没有什么特殊的要求,则 ThreadGroup 会将调用转发给     * 默认的未捕获异常处理器(即 Thread 类中定义的静态未捕获异常处理器对象)。     *     * @see #setDefaultUncaughtExceptionHandler     * @see #setUncaughtExceptionHandler     * @see ThreadGroup#uncaughtException     */    @FunctionalInterface    public interface UncaughtExceptionHandler {        /**         * 未捕获异常崩溃时回调此方法         */        void uncaughtException(Thread t, Throwable e);    }    /**     * 静态方法,用于设置一个默认的全局异常处理器。     */    public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {         defaultUncaughtExceptionHandler = eh;     }    /**     * 针对某个 Thread 对象的方法,用于对特定的线程进行未捕获的异常处理。     */    public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {        checkAccess();        uncaughtExceptionHandler = eh;    }    /**     * 当 Thread 崩溃时会调用该方法获取当前线程的 handler,获取不到就会调用 group(handler 类型)。     * group 是 Thread 类的 ThreadGroup 类型属性,在 Thread 构造中实例化。     */    public UncaughtExceptionHandler getUncaughtExceptionHandler() {        return uncaughtExceptionHandler != null ?            uncaughtExceptionHandler : group;    }    /**     * 线程全局默认 handler。     */    public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() {        return defaultUncaughtExceptionHandler;    }    ......}

部分内容参考自:https://mp.weixin.qq.com/s/ghnNQnpou6-NemhFjpl4Jg

线程池发生了异常该怎样处理?

线程池中线程运行过程中出现了异常该怎样处理呢?线程池提交任务有两种方式,分别是execute()和submit(),这里会依次说明。

ThreadPoolExecutor.runWorker()实现

不管是使用execute()还是submit()提交任务,最终都会执行到ThreadPoolExecutor.runWorker(),我们来看下源码(源码基于JDK1.8):

1fd4481f1f0459ef9c47c4ec3545cca5.png

runWorker().png

我们看到在执行task.run()时,出现异常会直接向上抛出,这里处理的最好的方式就是在我们业务代码中使用try...catch()来捕获异常。

FutureTask.run()实现

如果我们使用submit()来提交任务,在ThreadPoolExecutor.runWorker()方法执行时最终会调用到FutureTask.run()方法里面去,不清楚的小伙伴也可以看下我之前的文章:

线程池续:你必须要知道的线程池submit()实现原理之FutureTask!

cb2bcaf04ee820c4fef9a7c820e1722d.png

FutureTask.run().png

这里可以看到,如果业务代码抛出异常后,会被catch捕获到,然后调用setExeception()方法:

28829a17857d5582f1067282a3c134e5.png

FutureTask.setException().png

可以看到其实类似于直接吞掉了,当我们调用get()方法的时候异常信息会包装到FutureTask内部的变量outcome中,我们也会获取到对应的异常信息。

在ThreadPoolExecutor.runWorker()最后finally中有一个afterExecute()钩子方法,如果我们重写了afterExecute()方法,就可以获取到子线程抛出的具体异常信息Throwable了。

结论

对于线程池、包括线程的异常处理推荐以下方式:

  1. 直接使用try/catch,这个也是最推荐的方式
  2. 在我们构造线程池的时候,重写uncaughtException()方法,上面示例代码也有提到:
public class ThreadPoolExceptionTest {    public static void main(String[] args) throws InterruptedException {        MyHandler myHandler = new MyHandler();        ExecutorService execute = new ThreadPoolExecutor(10, 10,                0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), new ThreadFactoryBuilder().setUncaughtExceptionHandler(myHandler).build());        TimeUnit.SECONDS.sleep(5);        for (int i = 0; i 

3 直接重写afterExecute()方法,感知异常细节

总结

这篇文章到这里就结束了,不知道小伙伴们有没有一些感悟或收获?

通过这几个面试问题,我也深刻的感受到学习知识要多思考,看源码的过程中要多设置一些场景,这样才会收获更多。

950ffd00569313ee1d27ff3df29e4072.png

原创干货分享.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/446073.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux加密框架 crypto 算法管理 - 算法查找接口 crypto_larval_lookup

参考链接 Linux加密框架的算法管理&#xff08;二&#xff09;_家有一希的博客-CSDN博客 crypto_larval_lookup函数介绍 crypto_larval_lookup函数的输入参数包括待查找的算法名name、算法类型type和算法类型屏蔽位mask&#xff0c;查找命中时返回查找到的算法或注册用算法幼…

linux加密框架 crypto 算法管理 - 算法查找接口 crypto_alg_lookup函数

参考链接 Linux加密框架的算法管理&#xff08;二&#xff09;_家有一希的博客-CSDN博客 函数介绍 static struct crypto_alg *crypto_alg_lookup(const char *name, u32 type,u32 mask) {struct crypto_alg *alg;u32 test 0;if (!((type | mask) & CRYPTO_ALG_TESTED))…

linux加密框架 crypto 算法管理 - 动态和静态算法管理

参考链接 Linux加密框架的算法管理&#xff08;三&#xff09;_家有一希的博客-CSDN博客 动态和静态算法管理 静态算法 加密框架中的算法分为静态算法和动态算法两种&#xff0c;其中静态算法指的是以"算法名.ko"形式存在的静态编译的算法模块&#xff0c;如aes.k…

linux加密框架 crypto 算法管理 - 算法检测

参考链接 Linux加密框架的算法管理&#xff08;四&#xff09;_家有一希的博客-CSDN博客 函数介绍 如前所述&#xff0c;无论是静态算法还是动态算法&#xff0c;算法注册的最后一步都是进行算法正确性检验&#xff0c;一般流程是先调用__crypto_register_alg函数进行通用的算…

select选中的值_selenium下拉框处理(select)

前言 web自动化中&#xff0c;常见的场景还有一个下拉框的选择&#xff0c;哪么在selenium中如何做下拉框的操作呢&#xff1f;selectselect在HTML中表示元素名&#xff0c;可创建单选或多选菜单。HTML中select长什么样子&#xff1a;select在HTML中元素名&#xff0c;下面有选…

linux加密框架 crypto 算法管理 - 创建哈希算法实例

crypto_alloc_ahash函数 加密框架中的哈希算法可以是同步方式实现的也可以是异步方式实现的&#xff0c;但是算法应用不关注哈希算法的实现方式&#xff0c;关注的是哈希算法提供的算法接口。为实现统一管理&#xff0c;加密框架默认哈希算法的实现方式为异步方式&#xff0c;…

发票管理软件_企业为什么需要ERP企业管理软件?

对于一个制造企业来说&#xff0c;生产是企业最大的动力&#xff0c;而生产也需要进行优化管理&#xff0c;一个好的生产管理方式会带给企业巨大的发展空间和利润价值。对于一个制造企业来说&#xff0c;生产是企业最大的动力&#xff0c;而生产也需要进行优化管理&#xff0c;…

linux加密框架 crypto 算法管理 - 应用角度讲解加密框架的运行流程

参考链接 Linux加密框架的应用示例&#xff08;一&#xff09;_家有一希的博客-CSDN博客 本文大纲 本节将从应用角度说明加密框架的运行流程&#xff0c;包括加密框架如何管理算法、如何动态创建算法&#xff0c;应用模块如何创建算法实例、如何通过算法实例调用算法接口等。…

java 累进计费率计算_设计费400万,缴纳所得税100万,如何筹划

很多公司老板都会把利润放在第一位&#xff0c;照理说这是没错的&#xff0c;公司要盈利才能继续经营下去。我国有很多针对小微企业的政策&#xff0c;盈利不高的情况下&#xff0c;基本不会去考虑纳税问题&#xff0c;也没有多少税收压力。但是对一些暴利的服务型行业、软件设…

linux加密框架 crypto 算法管理 - 哈希算法应用实例

参考链接 Linux加密框架应用示例&#xff08;二&#xff09;_家有一希的博客-CSDN博客linux加密框架 crypto 算法管理 - 应用角度讲解加密框架的运行流程_CHYabc123456hh的博客-CSDN博客 在应用模块中创建并初始化哈希算法实例 假设某个SA配置使用的认证算法为"hmac(md5…

Linux加密框架 crypto crypto_larval | crypto_larval_alloc | __crypto_register_alg 介绍

参考链接 Lniux加密框架中的主要数据结构&#xff08;五&#xff09;_家有一希的博客-CSDN博客crypto_larval struct crypto_larval {struct crypto_alg alg;struct crypto_alg *adult;struct completion completion;u32 mask; };结构体名叫 crypto_larval &#xff08;算法幼…

好玩的脚本代码大全_Github | 推荐一个Python脚本集合项目

点击上方"蓝字"关注我们Python大数据分析记录 分享 成长用python写小脚本是一件好玩的事情&#xff0c;因为不是个大活儿&#xff0c;而且能解决眼边前十分繁琐的事情&#xff0c;这种轻松且便宜的代码颇受人民群众的欢迎~有点生活小妙招的意味大家较为熟知的脚本…

linux加密框架 crypto 算法管理 - 算法查找接口

参考链接 Linux加密框架的算法管理&#xff08;二&#xff09;_家有一希的博客-CSDN博客linux加密框架 crypto 算法管理 - 算法查找接口 crypto_find_alg_CHYabc123456hh的博客-CSDN博客linux加密框架 crypto 算法管理 - 算法查找接口 crypto_alg_mod_lookup_CHYabc123456hh的…

xml模糊查询语句_2Mybatis学习笔记07:动态SQL语句(原创,转载请注明来源)

开发环境&#xff1a;硬件环境&#xff1a;Windows10JDK 1.8&#xff1b; 软件环境&#xff1a;JavaEclipseMybatismaven3.6tomcat8.0Postgresql 10.6&#xff1b; 用到的jar包&#xff1a; asm-3.3.1.jar cglib-2.2.2.jar commons-logging-1.1.1.jar javassist-3.17.1-GA.jar …

硬件密码组件的硬件结构、作用及实现应用设计

引 言 1 硬件密码组件的概念 密码技术是解决信息安全问题的核心技术。要实现信息的保密性、完整性、可控性和不可否认性等安全要求&#xff0c;都离不开密码技术的运用。在具体的信息安全系统中&#xff0c;密码技术的运用可以基于软件密码组件&#xff08;简称为SCM&#xf…

sql倒序查询语句_SQL丨1.基本查询语句复习

此为自用查询语句1.selectSELECT column1,column2 FROM table1;常用的格式惯例&#xff1a;大写了SELECT和FROM&#xff0c;而将表名和列名小写&#xff1b;通常在列名中使用下划线&#xff0c;避免使用空格&#xff1b;在每个语句末尾添加分号&#xff1b;SQL不区分大小写。2.…

基于区块链的档案共享 项目启动

注意事项 已经备份了一个配置fabric的完整ubuntu系统&#xff0c;其需要注意的细节如下1&#xff0c;此镜像系统需要配置host文件&#xff0c;sudo vim /etc/hosts&#xff0c;添加如下内容127.0.0.1 orderer.example.com peer0.org1.example.com peer1.org1.example.c…

知道一点怎么设直线方程_【初中数学】反比例函数策略(二) ——构造方程法...

【相关阅读】【初中数学】反比例函数策略之一 ——数形结合反比例函数策略(二)——构造方程法(王 桥)上一次&#xff0c;咱们探讨了解决反比例函数的策略一——数形结合&#xff0c;本节课我们继续反比例函数的策略(二)——构造方程法。构造方程法&#xff0c;在《春季攻势》第…

wpf将文字转化为图形_将创新转化为实际应用

Worldsensing是全球公认的物联网先驱。这家位于西班牙巴塞罗那的技术供应商成立于2008年&#xff0c;为城市和传统行业提供运营情报。伊格纳西维拉霍萨纳(IgnasiVilajosana)是公司联合创始人兼首席执行官。伊格纳西拥有西班牙巴塞罗那大学物理学博士学位&#xff0c;还接受过美…

音视频处理 FFmpeg相关内容介绍 以及八大

FFmpeg的介绍 FFmpeg由Fabrice Bellard于2000年创建&#xff0c;由C和汇编语言进行开发FF -> Fast Forward 快进mpeg -> 标准化组织 Moving Pictures Experts Group使用到FFmpeg的开源项目gstreamer: a framework for streaming mediachromiummpv: Command line video pl…