为什么Spring 6中要把synchronized替换为ReentrantLock?

作为一名资深Spring爱好者,没事看看Spring的commit记录是一大爱好,这不,刚刚看了一下Spring最新的commit记录,其中有一条是这样的:

5天前提交的,标题意思是:为了避免thread pinning,所以修改了一些类中的代码,那具体修改了什么呢?我把重点给大家圈出来了:

代码中的writeLock定义为:

所以改动很明显了,就是去掉了synchronized,替换成为ReentrantLock,问题来了,为什么要这么做呢,synchronized不好用了?

我们先回到commit的标题:

我们需要解决3个问题:

1、什么是thread pinning?

2、为什么要避免thread pinning?

3、为什么把synchronized替换成ReentrantLock就可以避免thread pinning?

什么是thread pinning?

所谓thread pinning,就是线程绑定,有两种理解。

第一种,我们知道线程是在cpu上运行的,如果是多核cpu,那么同一个线程就可能一下是a核上运行,一下在b核上运行,而所谓线程绑定,就是把某一个线程固定在某个cpu核上运行。

第二种,跟虚拟线程有关,虚拟线程是JVM层面自己实现的,并不是真正的操作系统线程,并且虚拟线程是需要基于操作系统线程来运行的,因此虚拟线程和操作系统线程之间也存在绑定关系,正常情况下,一个虚拟线程可以运行在不同的操作系统线程之上,但是如果发生了thread pinning,那么就表示一个虚拟线程和一个操作系统线程绑定了,两者同生同死,那就不太好了。

而本文要分析的就是第二种情况,或者说这个commit要解决的就是第二种情况。

为什么synchronized会导致thread pinning?

当一个虚拟线程在获取锁时,如果获取不到,那么虚拟线程自身进行等待就可以了,可以把底层的操作系统线程释放掉,让它去服务其他的虚拟线程,没必要虚拟线程自己要等待,还要拉着底层操作系统线程一起等待。

但是当一个虚拟线程在执行某个加了synchronized关键字的方法时,糟糕的情况就出现了,这就会导致虚拟线程对应的底层操作系统线程要一起进行等待,从而占用了底层操作系统线程的资源,这本质上是"历史包袱",还需要给JVM底层来进行优化,因此,目前的解决方式就是使用ReentrantLock。

为什么ReentrantLock会避免thread pinning?

当一个虚拟线程在调用ReentrantLock的lock()方法进行加锁时,底层最终会调用LockSupport中的park()方法,而在这个方法中就对虚拟线程进行判断:

很明显,如果是虚拟线程就会单独进行处理,特别要注意的是,如果是虚拟线程,就不会进图中的else逻辑,也就不会对底层操作系统线程进行park,也就不会让底层操作系统线程进行等待。

VirtualThreads.park()最终会调用VirtualThread中的park():

然后调用:

然后调用:

这里似乎就是通过一个循环在进行等待?这块代码还没有深入研究,只能先这么理解了。

总之,ReentrantLock本质上就是对虚拟线程单独进行了优化,所以在加锁时,只需要虚拟线程自己等待就可以了,底层操作系统线程可以去干别的,这样就能提高底层操作系统线程的利用率。

再看一眼最开始的commit记录:

我总算知道Spring大佬为什么要避免thread pinning了,总算知道为什么要把synchronized替换成ReentrantLock了,原来是为了虚拟线程啊,赞!

我是IT周瑜,深耕Spring全家桶多年,欢迎关注我的个人公众号:IT周瑜,里面有更多干货文章。

相关推荐
余槐i3 小时前
PostgreSQL 函数索引报错 must be immutable:3种绕法与代价
数据库·后端·sql·postgresql·性能优化
需要8264 小时前
Spring Bean 生命周期:从实例化到销毁的完整钩子
java·后端·spring
小 黄 鸡4 小时前
HashMap 面试必会知识点详解(JDK 8)
java·面试·职场和发展
凤山老林5 小时前
Spring Boot 接入国密 SM2/SM3/SM4 的一些实践记录
spring boot·后端·python
伯恩bourne6 小时前
Spring Boot Bean 注册的两种方式:@Component 家族 vs @Configuration + @Bean
java·spring boot·后端
张彦峰ZYF6 小时前
停顿短不等于延迟低:G1、ZGC 与低延迟服务的目标分野、代价账本与选型标定
后端·回收暂停、应用停止总时长·到达安全点、分配停滞·屏障吃吞吐、并发线程吃核心·并发窗口吃堆余量·分区式靠挑回收集兑现目标·染色指针式不支持压缩引用
silvamux_6 小时前
curl 测试 OpenAI 兼容 API 的完整流程
java·人工智能·缓存·ai·oneapi
pride.li6 小时前
第03章 Python 教程-基础语法:注释与变量
服务器·前端·后端·python·算法·计算机视觉
省钱兄--zs7 小时前
深圳24小时自助健身房软硬件解决方案实战指南与开发流程详解
java·数据仓库·spring boot·小程序·架构
顽疲7 小时前
Java vue 养老系统源码详解:月账单 care_month_bill 与 Quartz 扣费 Job 实践
java·开发语言·vue.js·vue养老·springboot养老·java养老