如果你是 JDK 设计者,如何设计线程池?我跟面试官大战了三十个回合(中)

简介: 如果你是 JDK 设计者,如何设计线程池?我跟面试官大战了三十个回合(中)

来先吃我几问。

此时线程数小于核心线程数,并且线程都处于空闲状态,现提交一个任务,是新起一个线程还是给之前创建的线程?

李老是这样说的:If fewer than corePoolSize threads are running, try to start a new thread with the given command as its first task.

我觉得把 threads are running 去了,更合理一些,此时线程池会新起一个线程来执行这个新任务,不管老线程是否空闲。

你是如何理解核心线程的 ?

从上一个问题可以看出,线程池虽说默认是懒创建线程,但是它实际是想要快速拥有核心线程数的线程。核心线程指的是线程池承载日常任务的中坚力量,也就是说本质上线程池是需要这么些数量的线程来处理任务的,所以在懒中又急着创建它。

而最大线程数其实是为了应付突发状况。

举个装修的例子,正常情况下施工队只要 5 个人去干活,这 5 人其实就是核心线程,但是由于工头接的活太多了,导致 5 个人在约定工期内干不完,所以工头又去找了 2 个人来一起干,所以 5 是核心线程数,7 是最大线程数。

平时就是 5 个人干活,特别忙的时候就找 7 个,等闲下来就会把多余的 2 个辞了。

看到这里你可能会觉得核心线程在线程池里面会有特殊标记

并没有,不论是核心还是非核心线程,在线程池里面都是一视同仁,当淘汰的时候不会管是哪些线程,反正留下核心线程数个线程即可,下文会作详解。

你是怎么理解 KeepAliveTime 的?

这就是上面提到的,线程池其实想要的只是核心线程数个线程,但是又预留了一些数量来预防突发状况,当突发状况过去之后,线程池希望只维持核心线程数的线程,所以就弄了个 KeepAliveTime,当线程数大于核心数之后,如果线程空闲了一段时间(KeepAliveTime),就回收线程,直到数量与核心数持平。

那 workQueue 有什么用?

缓存任务供线程获取,这里要注意限制工作队列的大小。队列长了,堆积的任务就多,堆积的任务多,后面任务等待的时长就长。

想想你点击一个按钮是一直转圈等半天没反应舒服,还是直接报错舒服,所以有时心是好的,想尽量完成提交的任务,但是用户体验不如直接拒绝。更有可能由于允许囤积的任务过多,导致资源耗尽而系统崩溃

所以工作队列起到一个缓冲作用,具体队列长度需要结合线程数,任务的执行时长,能承受的等待时间等。

你是如何理解拒绝策略的?

线程数总有拉满的一天,工作队列也是一样,如果两者都满了,此时的提交任务就需要拒绝,默认实现是 AbortPolicy 直接抛出异常。


image.png


剩下的拒绝策略有直接丢弃任务一声不吭的、让提交任务的线程自己运行的、淘汰老的未执行的任务而空出位置的,具体用哪个策略,根据场景选择。当然也可以自定义拒绝策略,实现 RejectedExecutionHandler 这个接口即可。

所以线程池尽可能只维护核心数量的线程,提供任务队列暂存任务,并提供拒绝策略来应对过载的任务。

这里还有个细节,如果线程数已经达到核心线程数,那么新增加的任务只会往任务队列里面塞,不会直接给予某个线程,如果任务队列也满了,新增最大线程数的线程时,任务是可以直接给予新建的线程执行的,而不是入队

感觉已经会了?那再来看几道面试题:

你说你看过源码,那你肯定知道线程池里的 ctl 是干嘛的咯?


image.png


其实并发包中有很多实现都是一个字段存多个值的,比如读写锁的高 16 位存放读锁,低 16 位存放写锁,这种一个字段存放多个值可以更容易的维护多个值之间的一致性,也算是极简主义。

你知道线程池有几种状态吗?


image.png


注解说的很明白,我再翻译一下:

  • RUNNING:能接受新任务,并处理阻塞队列中的任务
  • SHUTDOWN:不接受新任务,但是可以处理阻塞队列中的任务
  • STOP:不接受新任务,并且不处理阻塞队列中的任务,并且还打断正在运行任务的线程,就是直接撂担子不干了!
  • TIDYING:所有任务都终止,并且工作线程也为0,处于关闭之前的状态
  • TERMINATED:已关闭。

你知道线程池的状态是如何变迁的吗?


image.png


image.png


为什么要把任务先放在任务队列里面,而不是把线程先拉满到最大线程数?

我说下我的个人理解。

其实经过上面的分析可以得知,线程池本意只是让核心数量的线程工作着,不论是 core 的取名,还是 keepalive 的设定,所以你可以直接把 core 的数量设为你想要线程池工作的线程数,而任务队列起到一个缓冲的作用。最大线程数这个参数更像是无奈之举,在最坏的情况下做最后的努力,去新建线程去帮助消化任务。

所以我个人觉得没有为什么,就是这样设计的,并且这样的设定挺合理。

当然如果你想要扯一扯 CPU 密集和 I/O 密集,那可以扯一扯。

原生版线程池的实现可以认为是偏向 CPU 密集的,也就是当任务过多的时候不是先去创建更多的线程,而是先缓存任务,让核心线程去消化,从上面的分析我们可以知道,当处理 CPU 密集型任务的时,线程太多反而会由于线程频繁切换的开销而得不偿失,所以优先堆积任务而不是创建新的线程。

而像 Tomcat 这种业务场景,大部分情况下是需要大量 I/O 处理的情况就做了一些定制,修改了原生线程池的实现,使得在队列没满的时候,可以创建线程至最大线程数。

如何修改原生线程池,使得可以先拉满线程数再入任务队列排队?

如果了解线程池的原理,很轻松的就知道关键点在哪,就是队列的 offer 方法。


image.png


execute 方法想必大家都不陌生,就是给线程池提交任务的方法。在这个方法中可以看到只要在 offer 方法内部判断此时线程数还小于最大线程数的时候返回 false,即可走下面 else ifaddWorker (新增线程)的逻辑,如果数量已经达到最大线程数,直接入队即可。

详细的我们可以看看 Tomcat 中是如何定制线程的。

Tomcat 中的定制化线程池实现

public class ThreadPoolExecutor extends java.util.concurrent.ThreadPoolExecutor {}

可以看到先继承了 JUC 的线程池,然后我们重点关注一下 execute 这个方法


image.png



相关文章
|
4天前
|
监控 Kubernetes Java
阿里面试:5000qps访问一个500ms的接口,如何设计线程池的核心线程数、最大线程数? 需要多少台机器?
本文由40岁老架构师尼恩撰写,针对一线互联网企业的高频面试题“如何确定系统的最佳线程数”进行系统化梳理。文章详细介绍了线程池设计的三个核心步骤:理论预估、压测验证和监控调整,并结合实际案例(5000qps、500ms响应时间、4核8G机器)给出具体参数设置建议。此外,还提供了《尼恩Java面试宝典PDF》等资源,帮助读者提升技术能力,顺利通过大厂面试。关注【技术自由圈】公众号,回复“领电子书”获取更多学习资料。
|
8天前
|
安全 Java 程序员
面试直击:并发编程三要素+线程安全全攻略!
并发编程三要素为原子性、可见性和有序性,确保多线程操作的一致性和安全性。Java 中通过 `synchronized`、`Lock`、`volatile`、原子类和线程安全集合等机制保障线程安全。掌握这些概念和工具,能有效解决并发问题,编写高效稳定的多线程程序。
49 11
|
7天前
|
Java Linux 调度
硬核揭秘:线程与进程的底层原理,面试高分必备!
嘿,大家好!我是小米,29岁的技术爱好者。今天来聊聊线程和进程的区别。进程是操作系统中运行的程序实例,有独立内存空间;线程是进程内的最小执行单元,共享内存。创建进程开销大但更安全,线程轻量高效但易引发数据竞争。面试时可强调:进程是资源分配单位,线程是CPU调度单位。根据不同场景选择合适的并发模型,如高并发用线程池。希望这篇文章能帮你更好地理解并回答面试中的相关问题,祝你早日拿下心仪的offer!
26 6
|
12天前
|
缓存 安全 算法
Java 多线程 面试题
Java 多线程 相关基础面试题
|
27天前
|
缓存 安全 Java
【JavaEE】——单例模式引起的多线程安全问题:“饿汉/懒汉”模式,及解决思路和方法(面试高频)
单例模式下,“饿汉模式”,“懒汉模式”,单例模式下引起的线程安全问题,解锁思路和解决方法
|
27天前
|
Java 调度
|
3月前
|
Dubbo Java 应用服务中间件
剖析Tomcat线程池与JDK线程池的区别和联系!
剖析Tomcat线程池与JDK线程池的区别和联系!
202 0
剖析Tomcat线程池与JDK线程池的区别和联系!
|
4月前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
|
4月前
|
消息中间件 前端开发 NoSQL
面试官:线程池遇到未处理的异常会崩溃吗?
面试官:线程池遇到未处理的异常会崩溃吗?
92 3
面试官:线程池遇到未处理的异常会崩溃吗?
|
4月前
|
消息中间件 存储 前端开发
面试官:说说停止线程池的执行流程?
面试官:说说停止线程池的执行流程?
65 2
面试官:说说停止线程池的执行流程?

相关实验场景

更多