作为一名资深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周瑜,里面有更多干货文章。