看到一个魔改线程池,面试素材加一!(下)

简介: 看到一个魔改线程池,面试素材加一!(下)

你到时候在这个地方打个断点,然后 Debug 看一眼,就非常明确了:


image.png

关于框起来的这部分的几个关键参数,我解释一下:

image.png

首先是 count 参数,就是我们定义的 3。那么 range(0,3),就是 0,1,2。

然后是 supplier,这玩意就是前面我们说的 executor 方法返回的 supplier 接口,可以看到里面封装的就是个线程池。

接着是里面有一个非常关键的操作 :map(ValueRef::new)。

这个操作里面的 ValueRef 对象,很关键:

com.github.phantomthief.pool.impl.KeyAffinityImpl.ValueRef


image.png

关键的地方就是这个对象里面的 concurrency 变量。

还记得最前面说的“挑选最闲置的执行器(线程池)”这句话吗?

怎么判断是否闲置?

靠的就是 concurrency 变量。

其对应的代码在这:

com.github.phantomthief.pool.impl.KeyAffinityImpl#select


image.png

能走到断点的地方,说明当前这个 key 是之前没有被映射过的,所以需要为其指定一个线程池。

而指定这个线程池的操作,就是循环这个 all 集合,集合里面装的就是 ValueRef 对象:


image.png

所以,comparingInt(ValueRef::concurrency) 方法就是在选当前所有的线程池,并发度最小的一个。

如果这个线程池从来没有用过或者目前没有任务在使用,那么并发度必然是 0 ,所有会被选出来。

如果所有线程池正在被使用,就会选 concurrency 这个值最低的线程池。

我这里只是给大家说一个大概的思路,如果要深入了解的话,自己去翻源码去。

如果你非常了解 lambdas 的用法的话,你会觉得写的真的很优雅,看起来很舒服。

如果你不了解 lambdas 的话...

那你还不赶紧去学?

另外我还发现了两个熟悉的东西。

朋友们,请看这是什么:

image.png

这难道不就是线程池参数的动态调整吗?

第二个是这样的:

image.png

RabbitMQ 里面的动态调整我也写过啊,也是强调过这三处地方:

  • 增加 {@link #setCapacity(int)} 和 {@link #getCapacity()}
  • {@link #capacity} 判断边界从 == 改为 >=
  • 部分 signal() 信号触发改为 signalAll()

另外作者还提到了 RabbitMQ 的版本里面会有导致 NPE 的 BUG 的问题。

这个就没细研究了,有兴趣的可以去对比一下代码,就应该能知道问题出在哪里。


说说 Dubbo


为什么要说一下 Dubbo 呢?

因为我似乎在 Dubbo 里面也发现了 KeyAffinityExecutor 的踪迹。

为什么说是似乎呢?

因为最终没有被合并到代码库里面去。

image.png

其对应的链接是这里:

https://github.com/apache/dubbo/pull/8975

这一次提交一共提交了这么多文件:

image.png


里面是可以找到我们熟悉的东西:


image.png

其实思路都是一样的,但是你会发现即使是思路一样,但是两个不同的人写出来的代码结构还是很不一样的。

Dubbo 这里把代码的层次分的更加明显一点,比如定义了一个抽象的 AbstractKeyAffinity 对象,然后在去实现了随机和最小并发两种方案。

在这些细节处上是有不同的。

但是这个代码的提供者最终没有用这些代码,而是拿出了一个替代方案:

image.png

https://github.com/apache/dubbo/pull/8999

在这一次提交里面,他主要提交了这个类:

org.apache.dubbo.common.threadpool.serial.SerializingExecutor

这个类从名字上你就知道了,它强调的是串行化。

带大家看看它的测试用例,你就知道它是怎么用的了:


image.png

首先是它的构造方法入参是另外一个线程池。

然后提交任务的时候用 SerializingExecutor 的 execute 方法进行提交。

在任务内部,干的事就是从 map 里面取出 val 对应的 key ,然后进行加 1 操作再放回去。

大家都知道上面的这个操作在多线程的情况是线程不安全的,最终加出来的结果一定是小于循环次数的。

但是,如果是单线程的情况下,那肯定是没问题的。

那么怎么把线程池映射为单线程呢?

SerializingExecutor 干得就是这事。

而且它的原理特别简单,核心代码就几行。

首先它自己搞了个队列:

image.png

提交进来的任务都扔到队列里面去。

接下来再一个个的执行。

怎么保证一个个的执行呢?

方法有很多,它这里是搞了个 AtomicBoolean 对象来控制:

image.png

这样就实现了把多线程任务搞成串行化的场景。

只是让我奇怪的是 SerializingExecutor 这个类目前在 Dubbo 里面并没有使用场景。

但是,如果你时候你就要实现这样奇怪的功能,比如别人给你一个线程池,但是到你的流程里面出入某种考虑,需要把任务串行化,这个时候肯定是不能动别人的线程池的,那么你可以想起 Dubbo 这里有一个现成的,比较优雅的、逼格较高的解决方案。


最后说一句


好了,看到了这里了, 转发、在看、点赞随便安排一个吧,要是你都安排上我也不介意。写文章很累的,需要一点正反馈。

给各位读者朋友们磕一个了:

微信图片_20220428223922.png

目录
相关文章
|
2月前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
2月前
|
消息中间件 前端开发 NoSQL
面试官:线程池遇到未处理的异常会崩溃吗?
面试官:线程池遇到未处理的异常会崩溃吗?
75 3
面试官:线程池遇到未处理的异常会崩溃吗?
|
2月前
|
消息中间件 存储 前端开发
面试官:说说停止线程池的执行流程?
面试官:说说停止线程池的执行流程?
51 2
面试官:说说停止线程池的执行流程?
|
2月前
|
消息中间件 前端开发 NoSQL
面试官:如何实现线程池任务编排?
面试官:如何实现线程池任务编排?
33 1
面试官:如何实现线程池任务编排?
|
3月前
|
Java
【多线程面试题二十五】、说说你对AQS的理解
这篇文章阐述了对Java中的AbstractQueuedSynchronizer(AQS)的理解,AQS是一个用于构建锁和其他同步组件的框架,它通过维护同步状态和FIFO等待队列,以及线程的阻塞与唤醒机制,来实现同步器的高效管理,并且可以通过实现特定的方法来自定义同步组件的行为。
【多线程面试题二十五】、说说你对AQS的理解
|
3月前
|
消息中间件 缓存 算法
Java多线程面试题总结(上)
进程和线程是操作系统管理程序执行的基本单位,二者有明显区别: 1. **定义与基本单位**:进程是资源分配的基本单位,拥有独立的内存空间;线程是调度和执行的基本单位,共享所属进程的资源。 2. **独立性与资源共享**:进程间相互独立,通信需显式机制;线程共享进程资源,通信更直接快捷。 3. **管理与调度**:进程管理复杂,线程管理更灵活。 4. **并发与并行**:进程并发执行,提高资源利用率;线程不仅并发还能并行执行,提升执行效率。 5. **健壮性**:进程更健壮,一个进程崩溃不影响其他进程;线程崩溃可能导致整个进程崩溃。
47 2
|
3月前
|
存储 缓存 安全
Java多线程面试题总结(中)
Java内存模型(JMM)定义了程序中所有变量的访问规则与范围,确保多线程环境下的数据一致性。JMM包含主内存与工作内存的概念,通过8种操作管理两者间的交互,确保原子性、可见性和有序性。`synchronized`和`volatile`关键字提供同步机制,前者确保互斥访问,后者保证变量更新的可见性。多线程操作涉及不同状态,如新建(NEW)、可运行(RUNNABLE)等,并可通过中断、等待和通知等机制协调线程活动。`volatile`虽不确保线程安全,但能确保变量更新对所有线程可见。
19 0
|
3月前
|
Java 程序员 容器
【多线程面试题二十四】、 说说你对JUC的了解
这篇文章介绍了Java并发包java.util.concurrent(简称JUC),它是JSR 166规范的实现,提供了并发编程所需的基础组件,包括原子更新类、锁与条件变量、线程池、阻塞队列、并发容器和同步器等多种工具。
|
1月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
43 1
C++ 多线程之初识多线程
|
23天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
17 3