我们再来继续深入 synchronized
从上文我们已经知道 synchronized 是作用于对象身上的,但是没细说,我们接下来剖析一波。
在 Java 中,对象结构分为对象头、实例数据和对齐填充。
而对象头又分为:MarkWord 、 klass pointer、数组长度(只有数组才有),我们的重点是锁,所以关注点只放在 MarkWord 上。
我再画一下 64 位时 MarkWord 在不同状态下的内存布局(里面的 monitor 打错了,但是我不准备改,留个印记哈哈)。
MarkWord 结构之所以搞得这么复杂,是因为需要节省内存,让同一个内存区域在不同阶段有不同的用处。
记住这个图啊,各种锁操作都和这个 MarkWord 有很强的联系。
从图中可以看到,在重量级锁时,对象头的锁标记位为 10,并且会有一个指针指向这个 monitor 对象,所以锁对象和 monitor 两者就是这样关联的。
而这个 monitor 在 HotSpot 中是 c++ 实现的,叫 ObjectMonitor,它是管程的实现,也有叫监视器的。
它长这样,重点字段我都注释了含义,还专门截了个头文件的注释:
暂时记忆一下,等下源码和这几个字段关联很大。
synchronized 底层原理
先来一张图,结合上面 monitor 的注释,先看看,看不懂没关系,有个大致流转的印象即可:
好,我们继续。
前面我们提到了 monitorenter 这个指令,这个指令会执行下面的代码:
我们现在分析的是重量级锁,所以不关心偏向的代码,而 slow_enter 方法文章一开始的截图就是了,最终会执行到 ObjectMonitor::enter
这个方法中。
可以看到重点就是通过 CAS 把 ObjectMonitor 中的 _owner 设置为当前线程,设置成功就表示获取锁成功。
然后通过 recursions 的自增来表示重入。
如果 CAS 失败的话,会执行下面的一个循环:
EnterI 的代码其实上面也已经截图了,这里再来一次,我把重要的入队操作加上,并且删除了一些不重要的代码:
先再尝试一下获取锁,不行的话就自适应自旋,还不行就包装成 ObjectWaiter 对象加入到 _cxq 这个单向链表之中,挣扎一下还是没抢到锁的话,那么就要阻塞了,所以下面还有个阻塞的方法。
可以看到不论哪个分支都会执行 Self->_ParkEvent->park()
,这个就是上文提到的调用 pthread_mutex_lock
。
至此争抢锁的流程已经很清晰了,我再画个图来理一理。
接下来再看看解锁的方法
ObjectMonitor::exit
就是解锁时会调用的方法。
可重入锁就是根据 _recursions 来判断的,重入一次 _recursions++,解锁一次 _recursions--,如果减到 0 说明需要释放锁了。
然后此时解锁的线程还会唤醒之前等待的线程,这里有好几种模式,我们来看看。
如果 QMode == 2 && _cxq != NULL
的时候:
至此,解锁的流程就完毕了!我再画一波流程图: