• Offer快到碗里来——聊聊线程池


    微信公众号:大黄奔跑
    关注我,可了解更多有趣的面试相关问题。

    写在之前

    Hello,大家好,我是只会写HelloWorld的程序员大黄。

    废话不多说,今天直接进入正题,聊聊Java中线程池的原理及面试问题。整体的文章会从面试题目展开,讲解面试点的过程中穿插源码的剖析。

    我个人比较推崇学习方式是,先知道某个技术是干嘛的,再了解为啥有这个技术,有什么好处。只有知道了为什么、才能更好的知道是什么。

    按照国际惯例先来看看一般面试中线程池会如何考察?

    面试问题概览

    下面是我在网上搜刮的一些关于线程池的面试题目,都是许多同学的亲身经历的。

    可以先看看这些面试题目,如果你面对这些题目,该如何回答呢?

    1. 线程池参数的具体含义【eBay一面】
    2. 创建线程的方式 线程池的主要参数,线程池在提交任务之后处理的过程【携程】
    3. 线程池了解吗?当程序用close()方法后,线程池里面的连接会关闭吗?【京东数科】
    4. 你了解哪些线程池类型【小鹏汽车】
    5. 线程池作用有哪些作用【招商银行】
    6. 说说线程池操作,参数【字节跳动】
    7. 说一下线程池的实现,参数有哪些,不同的使用场景分别用什么线程池【阿里巴巴】
    8. 线程池参数?线程池为什么用new的不好?【58面试】

    可以看到线程池相关的知识点被问到的概率还是挺大的,可以将问题做个分类,大概可以分为:
    第一类:线程池的使用(包括如何创建线程池、线程池各个参数含义、拒绝策略、什么场景会使用)
    第二类:线程池的原理了解(比如线程池底层实现、任务提交之后线程池如何工作的、线程池的执行过程)

    面试回顾

    面试一般面试上来肯定做一番自我介绍了,然后直奔主题。

    面试官:大黄同学,我看你简历中写了利用线程池解决项目中性能问题,那我们先简单聊聊线程池吧。说说线程池有哪些作用?

    哈哈哈,问吧,线程池我可是早就准备过的,送分题啊。

    大黄:面试官您好,线程池主要yo三个好处:

    1、降低资源的消耗。通过重复利用已经创建的线程降低线程创建和线程销毁的资源损耗。(用过的线程可以重复利用)

    2、提高响应速度。当任务达到时,不需要等待线程创建就可以执行。(任务来了就可以用)

    3、提供线程的可管理性。线程池本身提供了很多方法供用户监控、设置线程池。(创建一堆线程,有人帮你管理)

    面试官:那创建线程有哪些参数可以控制呢,他们分别有什么含义呢

    大黄:主要参数有

    1、corePoolSize:主要是线程池的核心线程数。当工作线程数小于该值时,新来的任务会创建新线程来处理,并且线程会一直存活,不会过期。
    2、maximumPoolSize:线程池的最大线程数。当工作中的线程数已经超过了最大线程数时,会默认启用拒绝策略
    3、keepAliveTime:线程空闲之后过期的时间,只有线程数大于corePoolSize时或者开启allowCoreThreadTimeOut参数时,该值才起作用
    4、unitkeepAliveTime的时间单位
    5、workQueue:阻塞队列,线程数超过corePoolSize时,请求的线程会先进入阻塞队列中,只有当阻塞队列也满了,才会去创建线程
    6、threadFactory:创建线程的工厂,所有的线程都是通过该工厂创建
    7、allowCoreThreadTimeOut:是否允许核心线程过期
    默认是false,此时,核心线程空闲也不会过期;如果是true,核心线程会等keepAliveTime时间,然后自动过期
    8、handler:线程满时或者停止时的拒绝策略

    读者可以看看线程池最全的构造方法

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
     
    {
        ....
    }

    面试官:可以详细说说线程池有哪些拒绝策略吗?

    大黄:线程池主要由以下四种拒绝策略

    1. 调用者线程来处理被拒绝的任务。比如我线程A提交任务给线程池,如果线程池此时处理不了,被拒绝了,则会交由线程A来处理(俗话说,哪儿来的回哪儿去

    2. 直接拒绝,并且往外抛出异常,该策略为JDK默认的拒绝策略。 线程池已经来不及处理了,偷懒,直接抛出异常。

    3. 直接废弃被拒绝的任务。线程A提交一个任务给线程池,线程池处理不了,直接不管了(俗称,装睡,假装不知道

    4. 废弃最早被提交未被处理的请求。比如线程A提交10任务给线程池,如果再提交新的任务给线程池,线程池会先抛出最先提交的任务(俗称,渣男,喜新厌旧

    咳咳,括号内的内容面试中就别回答了。。。

    面试官:平时有看过线程池的底层实现吗?能简单说说线程池底层是如何做的吗?

    其实这种问法跟上面"线程池在提交任务之后处理的过程?"本质问的问题是一样的

    大黄:线程池接受一个新提交的任务时线程池处理思路大概如下:

    1. 判断线程池工作中线程是否已经超过核心线程数(corePoolSize)。如果没有超过,则创建一个工作线程来执行任务;如果正在工作的线程数超过了核心线程数,则进入下一个流程。

    2. 判断线程池的阻塞队列(等待队列)是否已经被占满。如果没有占满,则将任务添加到阻塞队列中;如果已经占满,则进入下一阶段。

    3. 判断线程池工作中线程是否已经超过最大线程数。如果没有超过,则创建一个新的工作线程来执行任务;如果超过了最大线程数,则需要根据设置的拒绝策略来处理新的任务。

    大黄小提醒:可以用一个简单的例子描述,假设一个银行网点一般值班的有4个柜员,总共可以后备的柜员有10个,休息里面有可以做20人的凳子。刚开始业务不忙的时候,只有2个客户来办理业务,只要不超过4个客户,就可以用常用的值班柜员中找一个来服务;如果超过了4个,则可以让客户在休息区等待;如果等待任务超过了20个客户,还有新客户到来,则需要拉来未加班的同事了。

    整体的工作原理如下:

    线程池工作原理(图片思想借鉴于《Java并发编程艺术》)
    线程池工作原理(图片思想借鉴于《Java并发编程艺术》)

    大家可以记住这个例子,面试中不一定能够完整的回答上上面的所有内容,但是只要具体意思没有跑偏,面试官会领会到的。

    面试官:嗯,可以,了解的听清楚的嘛。那你平时工作中有地方用到线程池了吗?

    这点就是面试官比较聪明的地方,说了那么多,给我讲讲你工作的例子吧。正所谓:talk is cheap, show me your code.这里给大家说一个我工作中是使用的场景吧。

    大黄:我做的一个项目中为了快速的响应用户的请求。比如在用户获取具体的商品推荐策略中,需要查询商品数据和营销数据,然后将对应的商品信息和营销信息进行组装,返回给用户。
    刚开始用串行去请求,响应速度比较慢,用户迟迟查不到数据,如果一个页面半天都刷不出,用户可能就放弃查看这个商品了。为了解决这个问题,采用线程池,并发的请求商品和营销信息,缩短总体响应时间。

    并行请求

    并行请求
    并行请求

    串行请求

    串行请求
    串行请求

    使用线程池也是有考量的,这种场景最重要的就是获取最大的响应速度去满足用户,所以应该不设置队列去缓冲并发任务,调高corePoolSize和maxPoolSize去尽可能创造多的线程快速执行任务。

    面试官:那你项目中设置的线程池各个参数分别是多少呢?

    大黄:具体的线程池大小设置如下:

    @Bean
    ThreadPoolTaskExecutor ProductExecutor() {

        RejectedExecutionHandler rejectedExeHandler = (r, executor) -> {
            log.error("Executor_reject_handler");
            throw new Exception("System.error");
        };
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(200);
        executor.setMaxPoolSize(1000);
        executor.setKeepAliveSeconds(60);
        executor.setQueueCapacity(2000);
        executor.setRejectedExecutionHandler(rejectedExeHandler);
        return executor;
    }

    面试官:那一般项目线程池大小设置有什么规则吗?

    大黄:
    这种需要区分任务的类型,可以分为 CPU 密集型和 I/O 密集型,根据不同的任务类型,我们计算线程数的方法也不一样。
    1、CPU 密集型任务。
    这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。
    2、I/O 密集型任务
    这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N。

    上面的结论来自《Java并发编程艺术》,这种知识理论的设置,可以参考,但是实际中建议参考美团的技术博客《Java线程池实现原理及其在美团业务中的实践》

    面试官:好的,那我们聊聊别的吧。

    只要每个问题都可以回答到如此,何愁Offer。

    预告时间:下篇会仔细分析线程池的源码,可能会有点晦涩,我尽可能写的浅显一些。

    总结

    最后大黄分享多年面试心得。面试中,面对一个问题,大概按照总分的逻辑回答即可。先直接抛出结论,然后举例论证自己的结论。一定要第一时间抓住面试官的心里,否则容易给人抓不着重点或者不着边际的印象。

    本意是想着更可能回答的十全十美,无奈知识浅薄,难免会有纰漏,如果你发现了错误的地方,可以加我的微信交流。(微信公众号没有留言。)

    加个人微信,一起交流
    加个人微信,一起交流

    番外

    另外,关注大黄奔跑公众号,第一时间收获独家整理的面试实战记录及面试知识点总结。

    我是大黄,一个只会写HelloWorld的程序员,咱们下期见。

    关注大黄,充当offer收割机
    关注大黄,充当offer收割机
  • 相关阅读:
    58同城2018提前批前端笔试题总结
    两栏式布局和三栏式布局
    Less学习总结
    网易2018提前批前端笔试编程题
    编写一个函数isMerge,判断一个字符串str是否可以由其他两个字符串part1和part2“组合”而成
    【转】 解释下浏览器是如何判断元素是否匹配某个 CSS 选择器?
    JS数组精简的十三个技巧
    Docker常用命令(命令大全)
    ES6之新的数据结构
    JavaScript高级程序设计(第3版)每章小结(1-5)
  • 原文地址:https://www.cnblogs.com/xiaxj/p/14265918.html
Copyright © 2020-2023  润新知