Java锁-Synchronized深层剖析 (8)

说白了,Java的Monitor,就是JVM(如Hotspot)为每个对象建立的一个类似对象的实现,用于支持Monitor实现(实现了Monitor同步原语的各种功能)

上面这张图的下半部分,揭示了JVM(Hotspot)如何实现Monitor的,通过一个objectMonitor.cpp实现的。该cpp具有count,owner,WaitSet,EntryList等参数,还有monitorenter,monitorexit等方法。

看到这里,大家应该对Monitor不陌生了。一般说的Monitor,指两样东西:Monitor同步原语(类似协议,或者接口,规定了这个同步原语是如何实现同步功能的);Monitor实现(类似接口实现,协议落地代码等,就是具体实现功能的代码,如objectMonitor.cpp就是Hotspot的Monitor同步原语的落地实现)。两者的关系就是Java中接口和接口实现

Monitor实现重量级锁

那么monitor是如何实现重量级锁的呢?其实JVM通过Monitor实现Synchronized与JDK通过AQS实现ReentrantLock有异曲同工之妙。只不过JDK为了实现更好的功能扩展,从而搞了一个AQS,使得ReentrantLock看起来非常复杂而已,后续会开一个专门的系列,写AQS的。这里继续Monitor的分析。

在这里插入图片描述

从之前的objectMonitor.cpp的图中,可以看出:

objectMonitor有两个队列_EntryList和_WaitSet,两者都是用于保存objectWaiter对象的,其中**_EntryList用于保存等锁(线程状态为Block)的对象,而_WaitSet用于保存处于Wait线程状态(区别于Sleep线程状态,Wait线程状态的对象不仅会让出CPU,还会释放已占用的同步锁资源)的对象**。

_owner表示当前持有同步锁的objectWaiter对象。

_count则表示作为可重入锁的Synchronized的重入次数(否则,如何确定持有锁的线程是否完成了释放锁的操作呢)。

monitorenter与monitorexit主要负责加锁与释放锁的操作,不过由于Synchronized的可重入机制,所以需要对_count进行修改,并根据_count的值,判断是否释放锁,是否进行加锁等流程。

这个部分的代码逻辑不需要太过深入理解,只需要清楚明白关键参数的意义,以及大致流程即可。

有关具体重量级锁的底层ObjectMonitor源码解析,我就不再赘述,因为有一位大佬给出解析(我觉得挺好的,再深入就该去看源码了)。

如果真的希望清楚了解代码运行流程,又觉得看源码太过麻烦。可以查看我之后写的有关JUC下AQS对ReentrantLock的简化实现。看懂了那个,你会发现Monitor实现Synchronized的套路也就那样了(我自己就是这么过来的)。

Monitor与持有锁的线程

看完前面一部分的人,可能对如何实现Monitor,Monitor如何实现Synchronized已经很了解了。但是,Monitor如何与持有锁的线程产生关系呢?或者进一步问,之前提到的objectWaiter是个什么东西?

来,上图片。

在这里插入图片描述

从图中,可以清楚地看到,ObjectWaiter * _next与ObjectWaiter * _prev(volatile就不翻译,文章前面有),说明ObjectWaiter对象是一个双向链表结构。其中通过Thread* _thread来表示当前线程(即竞争锁的线程),通过TStates TState表示当前线程状态。这样一来,每个等待锁的线程都会被封装成OjbectWaiter对象,便于管理线程(这样一看,就和ReentrantLock更像了。ReentrantLock通过AQS的Node来封装等待锁的线程)。

补充

由于新到来锁竞争线程,会先尝试成为锁的持有者。在尝试失败后,才会切换线程状态为Block,并进入_EntryList。这就导致新到来的竞争锁的线程,可能在_EntryList不为空的情况下,直接持有同步锁,所以Synchronized为不公平锁。又由于该部分并没有提供别的加锁逻辑,所以Synchronized无法通过设置,改为公平锁。具体代码逻辑参照ReentrantLock。

notify()唤醒的是_WaitSet中任意一个线程,而不是根据等待时间确定的。

对象的notifyAll()或notify()唤醒的对象,不会从_WaitSet移动到_EntryList中,而是直接参与锁的竞争。竞争锁失败就继续在_WatiSet中等待,竞争锁成功就从_WaitSet中移除。这是从JVM性能方面考虑的:如元素在两个队列中移动的资源消耗,以及notify()唤醒的对象不一定能竞争锁成功,那么就需要再移动回_WaitSet。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpdpwz.html