面试官:你给我说一下线程池里面的几个锁吧。 (中)

简介: 面试官:你给我说一下线程池里面的几个锁吧。 (中)

另一把锁

除了前面说的 mainLock 外,线程池里面其实还有一把经常被大家忽略的锁。

那就是 Worker 对象。


image.png

可以看到 Worker 是继承自 AQS 对象的,它的很多方法也是和锁相关的。

image.png

同时它也实现了 Runnable 方法,所以说到底它就是一个被封装起来的线程,用来运行提交到线程池里面的任务,当没有任务的时候就去队列里面 take 或者 poll 等着,命不好的就被回收了。

我们还是看一下它加锁的地方,就在很关键的 runWorker 方法里面:

java.util.concurrent.ThreadPoolExecutor#runWorker

image.png


那么问题就来了:

这里是线程池里面的线程,正在执行提交的任务的逻辑的地方,为什么需要加锁呢?

这里为什么又自己搞了一个锁,而不用已有的 ReentrantLock ,即 mainLock 呢?

答案还是写在注释里面:

image.png

我知道你看着这么大一段英文瞬间就没有了兴趣。

但是别慌,我带你细嚼慢咽。

第一句话就开门见山的说了:

Class Worker mainly maintains interrupt control state for threads running tasks.

worker 类存在的主要意义就是为了维护线程的中断状态。

维护的线程也不是一般的线程,是 running tasks 的线程,也就是正在运行的线程。

怎么理解这个“维护线程的中断状态”呢?

你去看 Worker 类的 lock 和 tryLock 方法,都各自只有一个地方调用。

lock 方法我们前面说了,在 runWorker 方法里面调用了。

在 tryLock 方法是在这里调用的:

image.png

这个方法也是我们的老朋友了,前面刚刚才讲过,是用来中断线程的。

中断的是什么类型的线程呢?

image.png

就是正在等待任务的线程,即在这里等着的线程:

java.util.concurrent.ThreadPoolExecutor#getTask

image.png

换句话说:正在执行任务的线程是不应该被中断的。

那线程池怎么知道那哪任务是正在执行中的,不应该被中断呢?

我们看一下判断条件:


image.png


关键的条件其实就是 w.tryLock() 方法。

所以看一下 tryLock 方法里面的核心逻辑是怎么样的:


image.png


核心逻辑就是一个 CAS 操作,把某个状态从 0 更新为 1,如果成功了,就是 tryLock 成功。

“0”、“1” 分别是什么玩意呢?

注释,答案还是在注释里面:


image.png


所以,tryLock 中的核心逻辑compareAndSetState(0, 1),就是一个上锁的操作。

如果 tryLock 失败了,会是什么原因呢?

肯定是此时的状态已经是 1 了。

那么状态什么时候变成 1 呢?

一个时机就是执行 lock 方法的时候,它也会调用 tryAcquire 方法。

那 lock 是在什么时候上锁的呢?

runWorker 方法里面,获取到 task,准备执行的时候。

也就是说状态为 1 的 worker 肯定就是正在执行任务的线程,不可以被中断。

另外,状态的初始值被设置为 -1。

image.png

我们可以写个简单的代码,验证一下上面的三个状态:

image.png

首先我们定义一个线程池,然后调用 prestartAllCoreThreads 方法把所有线程都预热起来,让它们处于等待接收任务的状态。

你说这个时候,三个 worker 的状态分别是什么?

image.png

那必须得是 0 ,未上锁的状态。

当然了,你也有可能看到这样的局面:

image.png

-1 是从哪里来的呢?

别慌,我等下给你讲,我们先看看 1 在哪呢?

按照之前的分析,我们只需要往线程池里面提交一个任务即可:

微信图片_20220428213145.png


这个时候,假如我们调用 shutdown 呢,会发什么?

当然是中断空闲的线程了。

那正在执行任务的这个线程怎么办呢?

因为是个 while 循环,等到任务执行完成后,会再次调用 getTask 方法:

image.png

getTask 方法里面会先判断线程池状态,这个时候就能感知到线程池关闭了,返回 null,这个 worker 也就默默的退出了。


目录
相关文章
|
16天前
|
并行计算 算法 安全
面试必问的多线程优化技巧与实战
多线程编程是现代软件开发中不可或缺的一部分,特别是在处理高并发场景和优化程序性能时。作为Java开发者,掌握多线程优化技巧不仅能够提升程序的执行效率,还能在面试中脱颖而出。本文将从多线程基础、线程与进程的区别、多线程的优势出发,深入探讨如何避免死锁与竞态条件、线程间的通信机制、线程池的使用优势、线程优化算法与数据结构的选择,以及硬件加速技术。通过多个Java示例,我们将揭示这些技术的底层原理与实现方法。
68 3
|
2月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
15天前
|
Java 关系型数据库 MySQL
【JavaEE“多线程进阶”】——各种“锁”大总结
乐/悲观锁,轻/重量级锁,自旋锁,挂起等待锁,普通互斥锁,读写锁,公不公平锁,可不可重入锁,synchronized加锁三阶段过程,锁消除,锁粗化
|
2月前
|
供应链 安全 NoSQL
PHP 互斥锁:如何确保代码的线程安全?
在多线程和高并发环境中,确保代码段互斥执行至关重要。本文介绍了 PHP 互斥锁库 `wise-locksmith`,它提供多种锁机制(如文件锁、分布式锁等),有效解决线程安全问题,特别适用于电商平台库存管理等场景。通过 Composer 安装后,开发者可以利用该库确保在高并发下数据的一致性和安全性。
41 6
|
3月前
|
NoSQL Java API
美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴在面试一线互联网企业时遇到了关于Redis分布式锁过期及自动续期的问题。尼恩对此进行了系统化的梳理,介绍了两种核心解决方案:一是通过增加版本号实现乐观锁,二是利用watch dog自动续期机制。后者通过后台线程定期检查锁的状态并在必要时延长锁的过期时间,确保锁不会因超时而意外释放。尼恩还分享了详细的代码实现和原理分析,帮助读者深入理解并掌握这些技术点,以便在面试中自信应对相关问题。更多技术细节和面试准备资料可在尼恩的技术文章和《尼恩Java面试宝典》中获取。
美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
|
3月前
|
运维 API 计算机视觉
深度解密协程锁、信号量以及线程锁的实现原理
深度解密协程锁、信号量以及线程锁的实现原理
55 2
|
3月前
|
存储 Kubernetes 架构师
阿里面试:JVM 锁内存 是怎么变化的? JVM 锁的膨胀过程 ?
尼恩,一位经验丰富的40岁老架构师,通过其读者交流群分享了一系列关于JVM锁的深度解析,包括偏向锁、轻量级锁、自旋锁和重量级锁的概念、内存结构变化及锁膨胀流程。这些内容不仅帮助群内的小伙伴们顺利通过了多家一线互联网企业的面试,还整理成了《尼恩Java面试宝典》等技术资料,助力更多开发者提升技术水平,实现职业逆袭。尼恩强调,掌握这些核心知识点不仅能提高面试成功率,还能在实际工作中更好地应对高并发场景下的性能优化问题。
|
3月前
|
Java 应用服务中间件 测试技术
Java21虚拟线程:我的锁去哪儿了?
【10月更文挑战第8天】
58 0
|
3月前
|
安全 调度 数据安全/隐私保护
iOS线程锁
iOS线程锁
35 0
|
3月前
|
Java API
【多线程】乐观/悲观锁、重量级/轻量级锁、挂起等待/自旋锁、公平/非公锁、可重入/不可重入锁、读写锁
【多线程】乐观/悲观锁、重量级/轻量级锁、挂起等待/自旋锁、公平/非公锁、可重入/不可重入锁、读写锁
50 0