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收割机
posted @ 2021-01-12 10:57  SnailsCoffee  阅读(92)  评论(0编辑  收藏  举报