Open farmerjohngit opened 5 years ago
轻量级锁只要有竞争后就膨胀为重量级锁了嘛?好像没有先自旋一定次数失败再膨胀的逻辑,反而是膨胀后再自旋尝试。如果是这样的话,那么假如线程A获取了偏向锁,线程B再访问,然后偏向锁膨胀为轻量级锁,那线程B还是会继续尝试锁,那不就又直接膨胀为重量级锁了嘛,此时的轻量级锁的意义不就没了- -
@pigeonsoar
那线程B还是会继续尝试锁,那不就又直接膨胀为重量级锁了嘛
B获得轻量级锁后,如没有其他线程获取锁就一直是轻量级锁
@pigeonsoar
那线程B还是会继续尝试锁,那不就又直接膨胀为重量级锁了嘛
B获得轻量级锁后,如没有其他线程获取锁就一直是轻量级锁
如果是A线程的偏向锁升级到了轻量级锁,A获取的轻量级锁还未解锁状态下,线程B继续尝试获取锁一次就会升级到重量级锁?
自旋获取轻量级锁的代码是在哪,我怎么没找到。。
您好 看了您的文章受益匪浅 这里有一个小问题 重量级锁级锁时三个队列都没有线程,markword是不是会被替换为无锁状态001,我代码测试的结果是会替换为001,然后再次加锁会从轻量级锁开始,这属于锁降级吗?
线程 T2 是轻量锁并且正在执行, T3 执行会尝试获取锁, 那么是由 T3 升级锁, 还是 T2 来升级锁呢?
系列看完了,确实是良心作品!学习了!
至于为什么轻量级锁需要一个膨胀中(INFLATING)状态,代码中的注释是:
// Why do we CAS a 0 into the mark-word instead of just CASing the // mark-word from the stack-locked value directly to the new inflated state? ... 我没太看懂,有知道的同学可以指点下~
本人拙见,设置为0主要是为了避免如下两个场景发生:
the 0 causes the owner to stall if the owner happens to try to drop the lock (restoring the header from the basiclock to the object) while inflation is in-progress.
设置为0可以实现,当锁正在膨胀为重量级锁时,延迟拥有锁的线程释放锁。 这里说的锁,就是轻量级锁。 延迟的实现逻辑就是,在释放锁时进入inflate方法,判断到是膨胀中,就进行忙等
This protocol avoids races that might would otherwise permit hashCode values to change or "flicker" for an object.
另一方面,设置为0也避免了获取对象的hash code值与锁膨胀的竞争问题 举例场景:
线程 T2 是轻量锁并且正在执行, T3 执行会尝试获取锁, 那么是由 T3 升级锁, 还是 T2 来升级锁呢?
T3来升级,会将锁的_owner设置为T2,这样就不会影响T2使用锁
看完重量级锁的膨胀过程后,一直有个问题没想明白: 如果在膨胀前A线程已经重入了3次轻量级锁,这时B线程来竞争锁,导致锁膨胀。膨胀过程中,会将_owner设置为A线程堆栈中的Lock Record(其实就是锁对象指向的Lock Record地址)。但是,这里没有处理锁重入次数(只是将monitor的__ecursions设置为了0),且在锁释放过程中,也没考虑这个问题。 那么这3次轻量级锁重入是否丢失了呢?
希望博主和一起学习的同志能指导一下
看完重量级锁的膨胀过程后,一直有个问题没想明白: 如果在膨胀前A线程已经重入了3次轻量级锁,这时B线程来竞争锁,导致锁膨胀。膨胀过程中,会将_owner设置为A线程堆栈中的Lock Record(其实就是锁对象指向的Lock Record地址)。但是,这里没有处理锁重入次数(只是将monitor的__ecursions设置为了0),且在锁释放过程中,也没考虑这个问题。 那么这3次轻量级锁重入是否丢失了呢?
希望博主和一起学习的同志能指导一下
个人拙见:线程在释放重入的2,3次的锁时,只是简单的将lock record中的obj reference置为null,并不关心锁对象的状态,只有当最外层锁(也就是lock record中displace mark word记录着锁对象的mark word)释放时,会回写mark word到锁对象,这个时候失败了才会进入到膨胀环节.此时只有第一次的锁需要释放,所以在膨胀的时候重入计数并不需要处理.
线程 T2 是轻量锁并且正在执行, T3 执行会尝试获取锁, 那么是由 T3 升级锁, 还是 T2 来升级锁呢?
T3来升级,会将锁的_owner设置为T2,这样就不会影响T2使用锁
感谢大佬解答!
问一下博主,JDK1.6之前的重量级锁实现,为什么是重量的呢?因为现在的重量级锁在加锁的时候不会无脑进行系统调用,而是会通过乐观锁判断时候存在竞争,有竞争才会加锁,其原理基本和reentrantlock一样了,何来“重量一说”?难道是1.6以后重量级锁的逻辑也改了?
问一下博主,JDK1.6之前的重量级锁实现,为什么是重量的呢?因为现在的重量级锁在加锁的时候不会无脑进行系统调用,而是会通过乐观锁判断时候存在竞争,有竞争才会加锁,其原理基本和reentrantlock一样了,何来“重量一说”?难道是1.6以后重量级锁的逻辑也改了?
应该来说重量级锁就是利用了阻塞队列来实现了独占锁
博主你好,我发现这个地方在前后两篇文章中描述冲突了。 issues/12:当一个线程尝试获得锁时,如果该锁已经被占用,则会将该线程封装成一个ObjectWaiter对象插入到cxq的队列尾部 issues/15:当一个线程尝试获得锁时,如果该锁已经被占用,则会将该线程封装成一个ObjectWaiter对象插入到cxq的队列的队首
默认策略下,在A释放锁后一定是C线程先获得锁。因为在获取锁时,是将当前线程插入到cxq的头部,而释放锁时,默认策略是:如果EntryList为空,则将cxq中的元素按原有顺序插入到到EntryList,并唤醒第一个线程。也就是当EntryList为空时,是后来的线程先获取锁。这点JDK中的Lock机制是不一样的。
这个是导致synchronized是非公平锁的原因吗?
@M0rnar
// code 1:先释放锁,这时如果有其他线程进入同步块则能获得锁 OrderAccess::release_store_ptr (&_owner, NULL) ; // drop the lock OrderAccess::storeload() ; // See if we need to wake a successor
你说的算是一个原因,这里也有一个
自旋获取轻量级锁的代码是在哪,我怎么没找到。。
轻量级应该是没有自旋,只有一次CAS的机会,如果失败了就会升级。在重量级阶段里,有多处自旋获取重量级锁的逻辑tryLock方法就是。我感觉自旋获取轻量级锁是被其他文章误导了,在看源码之前我也一直认为自旋是获取轻量级锁来着
博主你好,我发现这个地方在前后两篇文章中描述冲突了。 issues/12:当一个线程尝试获得锁时,如果该锁已经被占用,则会将该线程封装成一个ObjectWaiter对象插入到cxq的队列尾部 issues/15:当一个线程尝试获得锁时,如果该锁已经被占用,则会将该线程封装成一个ObjectWaiter对象插入到cxq的队列的队首
看源码如下 将该线程的ObjectWaiter的next指向cxq的头节点(猜的,不懂cpp),所以应该是插入到cxq的队首
// Push "Self" onto the front of the _cxq.
// Once on cxq/EntryList, Self stays on-queue until it acquires the lock.
// Note that spinning tends to reduce the rate at which threads
// enqueue and dequeue on EntryList|cxq.
ObjectWaiter * nxt; for (;;) { node._next = nxt = _cxq; ... }
您好,我想请问一下,偏向锁的CAS和轻量级锁的CAS有没有什么不同,V、A、B分别都是什么值。
@pigeonsoar
那线程B还是会继续尝试锁,那不就又直接膨胀为重量级锁了嘛
B获得轻量级锁后,如没有其他线程获取锁就一直是轻量级锁
如果是A线程的偏向锁升级到了轻量级锁,A获取的轻量级锁还未解锁状态下,线程B继续尝试获取锁一次就会升级到重量级锁?
确实这里好像是这样,只是不知道具体流程是什么。
假设虚拟机启动一段时间后偏向锁模式已开启,线程A最初进入synchronized时完全没有竞争,这时候是偏向锁,但线程A这个同步代码块执行了很长很长的时间。 期间线程B开始进入synchronized,线程B是不是得在一次bytecodeInterpreter.cpp#CASE(_monitorenter)的执行中先撤销偏向锁升级到轻量级锁,然后再膨胀到重量级锁并进入获取锁的等待?
@pigeonsoar
那线程B还是会继续尝试锁,那不就又直接膨胀为重量级锁了嘛
B获得轻量级锁后,如没有其他线程获取锁就一直是轻量级锁
如果是A线程的偏向锁升级到了轻量级锁,A获取的轻量级锁还未解锁状态下,线程B继续尝试获取锁一次就会升级到重量级锁?
确实这里好像是这样,只是不知道具体流程是什么。
假设虚拟机启动一段时间后偏向锁模式已开启,线程A最初进入synchronized时完全没有竞争,这时候是偏向锁,但线程A这个同步代码块执行了很长很长的时间。 期间线程B开始进入synchronized,线程B是不是得在一次bytecodeInterpreter.cpp#CASE(_monitorenter)的执行中先撤销偏向锁升级到轻量级锁,然后再膨胀到重量级锁并进入获取锁的等待?
我打印了T1此时的锁状态,不明白为什么是重量级锁,既然线程A一直占用着锁,按照偏向锁撤销那一套逻辑,为什么不是轻量级锁呢?不理解output2为什么不是轻量级锁??
public static void main(String[] args) throws InterruptedException {
Object d = new Object();
new Thread(()->{
synchronized (d) {
System.out.println("output1\t" + ClassLayout.parseInstance(d).toPrintable());
//ouput 1 : 偏向锁
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("output2\t" + ClassLayout.parseInstance(d).toPrintable());
//output2 :重量级锁
}
}).start();
new Thread(()->{
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (d) {
System.out.println("output3\t" + ClassLayout.parseInstance(d).toPrintable());
// output3:重量级锁
}
}).start();
}
output1 java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000002be5298b805 (biased: 0x00000000af94a62e; epoch: 0; age: 0)
8 8 (object header: class) 0x000002be4ff51c00
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
output2 java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000002be51352f1a (fat lock: 0x000002be51352f1a)
8 8 (object header: class) 0x000002be4ff51c00
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
output3 java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000002be51352f1a (fat lock: 0x000002be51352f1a)
8 8 (object header: class) 0x000002be4ff51c00
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
Process finished with exit code 0
@pigeonsoar
那线程B还是会继续尝试锁,那不就又直接膨胀为重量级锁了嘛
B获得轻量级锁后,如没有其他线程获取锁就一直是轻量级锁
如果是A线程的偏向锁升级到了轻量级锁,A获取的轻量级锁还未解锁状态下,线程B继续尝试获取锁一次就会升级到重量级锁?
A未解,但B已经膨胀,所以此时锁状态是怎样子的呢?轻量级锁还是重量级锁呢?
您好,我想请问一下,偏向锁的CAS和轻量级锁的CAS有没有什么不同,V、A、B分别都是什么值。
@pigeonsoar
那线程B还是会继续尝试锁,那不就又直接膨胀为重量级锁了嘛
B获得轻量级锁后,如没有其他线程获取锁就一直是轻量级锁
如果是A线程的偏向锁升级到了轻量级锁,A获取的轻量级锁还未解锁状态下,线程B继续尝试获取锁一次就会升级到重量级锁?
A未解,但B已经膨胀,所以此时锁状态是怎样子的呢?轻量级锁还是重量级锁呢?
此时锁已经升级成重量级了,A会变成锁的持有者,B在尝试获取几次后,会被加入到队列中(EntryList还是cxq记不太清了),等待A释放后B会继续尝试获取
搁浅现象是啥,谁能科普一下
看了很多文章,关于偏向锁、轻量级锁和重量级锁,偏向锁基于该锁持有者长时间为单一线程,而轻量级锁则是多个线程交替使用,重量级则发生争夺,但说锁升级的时候都是偏向锁->轻量级,那如果再持有轻量级锁的过程中,另一个线程来争抢锁,这个锁是直接升级为重量级锁还是先升级为轻量级锁
图片连接失效了
搁浅现象是啥,谁能科普一下
搁浅(stranding)现象: monitor 已解锁,但所有争用线程仍处于休眠状态。此时如果没有新的线程来获取锁,已解锁的 monitor 永远无法被正在休眠的线程获取。为了避免这种问题,会选择一个线程作为负责线程(responsible thread),该线程通过调用 time park() 方法,让自己休眠固定时间后被唤醒,随后该线程会检查潜在搁浅问题并从中恢复
锁膨胀过程中。若当前是轻量级锁。创建ObjectMonitor对象后,为什么可以简单地将其_recursions设置为0?如果是锁膨胀时,轻量级锁存在重入的情况怎么办?
。若当前是轻量级锁。创建ObjectMonitor对象后,为什么可以简单地将其_recursions设置为0?如果是锁膨胀时
轻量级锁升级为重量级锁,初始状态当然得为 0,因为没有发生重入。膨胀过程中,持有锁的线程重入时,会检测到锁在膨胀过程,不能走原有的轻量级锁重入加锁逻辑,会和其他线程一样其他线程一样进行等待(以自旋方式等待),直到膨胀完成,膨胀完成后,持有锁的线程走的就是正常的重量级锁重入逻辑了。
。若当前是轻量级锁。创建ObjectMonitor对象后,为什么可以简单地将其_recursions设置为0?如果是锁膨胀时
轻量级锁升级为重量级锁,初始状态当然得为 0,因为没有发生重入。膨胀过程中,持有锁的线程重入时,会检测到锁在膨胀过程,不能走原有的轻量级锁重入加锁逻辑,会和其他线程一样其他线程一样进行等待(以自旋方式等待),直到膨胀完成,膨胀完成后,持有锁的线程走的就是正常的重量级锁重入逻辑了。
为什么不会存在锁重入的情况呢?分析以下情况: 1.当前obj是轻量级锁状态。线程A成功拿到轻量级锁,并且正在执行code1处的业务逻辑。 2.此时,线程B也执行到process方法。发生锁竞争,这时,线程B把obj膨胀为重量级锁(我分析锁膨胀的逻辑,是任何线程都可以负责锁膨胀的操作的,只要它能够把锁对象的markword设置为0,就是由它负责膨胀)。 3.线程B完成锁膨胀,把ObjectMonitor._recursions设置0。这难道没问题吗?如果这样,线程A执行完code1并退出内部的synchronized后,不是就会把锁释放了吗?
public static Object obj = new Object();
public static void process() {
synchronized (obj) {
synchronized (obj) {
//code 1.业务处理过程1。
}
//code 2.业务处理过程2
}
}
。若当前是轻量级锁。创建ObjectMonitor对象后,为什么可以简单地将其_recursions设置为0?如果是锁膨胀时
轻量级锁升级为重量级锁,初始状态当然得为 0,因为没有发生重入。膨胀过程中,持有锁的线程重入时,会检测到锁在膨胀过程,不能走原有的轻量级锁重入加锁逻辑,会和其他线程一样其他线程一样进行等待(以自旋方式等待),直到膨胀完成,膨胀完成后,持有锁的线程走的就是正常的重量级锁重入逻辑了。
为什么不会存在锁重入的情况呢?分析以下情况: 1.当前obj是轻量级锁状态。线程A成功拿到轻量级锁,并且正在执行code1处的业务逻辑。 2.此时,线程B也执行到process方法。发生锁竞争,这时,线程B把obj膨胀为重量级锁(我分析锁膨胀的逻辑,是任何线程都可以负责锁膨胀的操作的,只要它能够把锁对象的markword设置为0,就是由它负责膨胀)。 3.线程B完成锁膨胀,把ObjectMonitor._recursions设置0。这难道没问题吗?如果这样,线程A执行完code1并退出内部的synchronized后,不是就会把锁释放了吗?
public static Object obj = new Object(); public static void process() { synchronized (obj) { synchronized (obj) { //code 1.业务处理过程1。 } //code 2.业务处理过程2 } }
轻量级锁释放时, 会判断 Lock Record 记录的 mark word 值是否为 0(即 NULL)
轻量级锁在重入多次后,发生了锁膨胀,此时当轻量级锁释放时,和正常的轻量级锁释放时流程一样,只不过在 Lock Record 记录的 mark word 为 0 的情况下会增加如下诊断:
对于轻量级锁膨胀后,之前的重入释放操作和之前一样,不会走 ObjectMonitor exit 操作,因此在轻量级锁多次重入,然后被膨胀时,ObjectMonitor 的重入计数没必要记录之前的重入次数,使用初始值 0 即可。
。若当前是轻量级锁。创建ObjectMonitor对象后,为什么可以简单地将其_recursions设置为0?如果是锁膨胀时
轻量级锁升级为重量级锁,初始状态当然得为 0,因为没有发生重入。膨胀过程中,持有锁的线程重入时,会检测到锁在膨胀过程,不能走原有的轻量级锁重入加锁逻辑,会和其他线程一样其他线程一样进行等待(以自旋方式等待),直到膨胀完成,膨胀完成后,持有锁的线程走的就是正常的重量级锁重入逻辑了。
为什么不会存在锁重入的情况呢?分析以下情况: 1.当前obj是轻量级锁状态。线程A成功拿到轻量级锁,并且正在执行code1处的业务逻辑。 2.此时,线程B也执行到process方法。发生锁竞争,这时,线程B把obj膨胀为重量级锁(我分析锁膨胀的逻辑,是任何线程都可以负责锁膨胀的操作的,只要它能够把锁对象的markword设置为0,就是由它负责膨胀)。 3.线程B完成锁膨胀,把ObjectMonitor._recursions设置0。这难道没问题吗?如果这样,线程A执行完code1并退出内部的synchronized后,不是就会把锁释放了吗?
public static Object obj = new Object(); public static void process() { synchronized (obj) { synchronized (obj) { //code 1.业务处理过程1。 } //code 2.业务处理过程2 } }
轻量级锁释放时, 会判断 Lock Record 记录的 mark word 值是否为 0(即 NULL)
- 如果为 0 则表示是重入的释放,不做任何处理.
- 如果不是进行真正的释放操作,通过 CAS 更新锁对象 mark word 为 Lock Record 中记录的值, 如果失败则表示期间锁发生了膨胀,此时会走锁膨胀,然后释放膨胀后的重量级锁(ObjectMonitor)的逻辑
轻量级锁在重入多次后,发生了锁膨胀,此时当轻量级锁释放时,和正常的轻量级锁释放时流程一样,只不过在 Lock Record 记录的 mark word 为 0 的情况下会增加如下诊断:
- ObjectMonitor 关联的锁对象的 mark word 必须与 exit 对应的锁对象相等,不是抛出异常.
- ObjectMonitor 是否当前线程获取,不是抛出异常.
对于轻量级锁膨胀后,之前的重入释放操作和之前一样,不会走 ObjectMonitor exit 操作,因此在轻量级锁多次重入,然后被膨胀时,ObjectMonitor 的重入计数没必要记录之前的重入次数,使用初始值 0 即可。
明白了
本文为死磕Synchronized底层实现第三篇文章,内容为重量级锁实现。
本系列文章将对HotSpot的
synchronized
锁实现进行全面分析,内容包括偏向锁、轻量级锁、重量级锁的加锁、解锁、锁升级流程的原理及源码分析,希望给在研究synchronized
路上的同学一些帮助。主要包括以下几篇文章:死磕Synchronized底层实现--概论
死磕Synchronized底层实现--偏向锁
死磕Synchronized底层实现--轻量级锁
死磕Synchronized底层实现--重量级锁
更多文章见个人博客:https://github.com/farmerjohngit/myblog
重量级的膨胀和加锁流程
当出现多个线程同时竞争锁时,会进入到
synchronizer.cpp#slow_enter
方法在
inflate
中完成膨胀过程。inflate
中是一个for循环,主要是为了处理多线程同时调用inflate的情况。然后会根据锁对象的状态进行不同的处理:1.已经是重量级状态,说明膨胀已经完成,直接返回
2.如果是轻量级锁则需要进行膨胀操作
3.如果是膨胀中状态,则进行忙等待
4.如果是无锁状态则需要进行膨胀操作
其中轻量级锁和无锁状态需要进行膨胀操作,轻量级锁膨胀流程如下:
1.调用
omAlloc
分配一个ObjectMonitor
对象(以下简称monitor),在omAlloc
方法中会先从线程私有的monitor
集合omFreeList
中分配对象,如果omFreeList
中已经没有monitor
对象,则从JVM全局的gFreeList
中分配一批monitor
到omFreeList
中。2.初始化
monitor
对象3.将状态设置为膨胀中(INFLATING)状态
4.设置
monitor
的header字段为displaced mark word
,owner字段为Lock Record
,obj字段为锁对象5.设置锁对象头的
mark word
为重量级锁状态,指向第一步分配的monitor
对象无锁状态下的膨胀流程如下:
1.调用
omAlloc
分配一个ObjectMonitor
对象(以下简称monitor)2.初始化
monitor
对象3.设置
monitor
的header字段为mark word
,owner字段为null
,obj字段为锁对象4.设置锁对象头的
mark word
为重量级锁状态,指向第一步分配的monitor
对象至于为什么轻量级锁需要一个膨胀中(INFLATING)状态,代码中的注释是:
我没太看懂,有知道的同学可以指点下~
膨胀完成之后,会调用
enter
方法获得锁EnterI
方法获得锁或阻塞EnterI
方法比较长,在看之前,我们先阐述下其大致原理:一个
ObjectMonitor
对象包括这么几个关键字段:cxq(下图中的ContentionList),EntryList ,WaitSet,owner。其中cxq ,EntryList ,WaitSet都是由ObjectWaiter的链表结构,owner指向持有锁的线程。
当一个线程尝试获得锁时,如果该锁已经被占用,则会将该线程封装成一个
ObjectWaiter
对象插入到cxq的队列的队首,然后调用park
函数挂起当前线程。在linux系统上,park
函数底层调用的是gclib库的pthread_cond_wait
,JDK的ReentrantLock
底层也是用该方法挂起线程的。更多细节可以看我之前的两篇文章:关于同步的一点思考-下,linux内核级同步机制--futex当线程释放锁时,会从cxq或EntryList中挑选一个线程唤醒,被选中的线程叫做
Heir presumptive
即假定继承人(应该是这样翻译),就是图中的Ready Thread
,假定继承人被唤醒后会尝试获得锁,但synchronized
是非公平的,所以假定继承人不一定能获得锁(这也是它叫"假定"继承人的原因)。如果线程获得锁后调用
Object#wait
方法,则会将线程加入到WaitSet中,当被Object#notify
唤醒后,会将线程从WaitSet移动到cxq或EntryList中去。需要注意的是,当调用一个锁对象的wait
或notify
方法时,如当前锁的状态是偏向锁或轻量级锁则会先膨胀成重量级锁。synchronized
的monitor
锁机制和JDK的ReentrantLock
与Condition
是很相似的,ReentrantLock
也有一个存放等待获取锁线程的链表,Condition
也有一个类似WaitSet
的集合用来存放调用了await
的线程。如果你之前对ReentrantLock
有深入了解,那理解起monitor
应该是很简单。回到代码上,开始分析
EnterI
方法:主要步骤有3步:
这里需要特别说明的是
_Responsible
和_succ
两个字段的作用:当竞争发生时,选取一个线程作为
_Responsible
,_Responsible
线程调用的是有时间限制的park
方法,其目的是防止出现搁浅
现象。_succ
线程是在线程释放锁是被设置,其含义是Heir presumptive
,也就是我们上面说的假定继承人。重量级锁的释放
重量级锁释放的代码在
ObjectMonitor::exit
:在进行必要的锁重入判断以及自旋优化后,进入到主要逻辑:
code 1
设置owner为null,即释放锁,这个时刻其他的线程能获取到锁。这里是一个非公平锁的优化;code 2
如果当前没有等待的线程则直接返回就好了,因为不需要唤醒其他线程。或者如果说succ不为null,代表当前已经有个"醒着的"继承人线程,那当前线程不需要唤醒任何线程;code 3
当前线程重新获得锁,因为之后要操作cxq和EntryList队列以及唤醒线程;code 4
根据QMode的不同,会执行不同的唤醒策略;根据QMode的不同,有不同的处理方式:
只有QMode=2的时候会提前返回,等于0、3、4的时候都会继续往下执行:
1.如果EntryList的首元素非空,就取出来调用ExitEpilog方法,该方法会唤醒ObjectWaiter对象的线程,然后立即返回; 2.如果EntryList的首元素为空,就将cxq的所有元素放入到EntryList中,然后再从EntryList中取出来队首元素执行ExitEpilog方法,然后立即返回;
以上对QMode的归纳参考了这篇文章。另外说下,关于如何编译JVM,可以看看该博主的这篇文章,该博主弄了一个docker镜像,傻瓜编译~
QMode默认为0,结合上面的流程我们可以看这么个demo:
默认策略下,在A释放锁后一定是C线程先获得锁。因为在获取锁时,是将当前线程插入到cxq的头部,而释放锁时,默认策略是:如果EntryList为空,则将cxq中的元素按原有顺序插入到到EntryList,并唤醒第一个线程。也就是当EntryList为空时,是后来的线程先获取锁。这点JDK中的Lock机制是不一样的。
Synchronized和ReentrantLock的区别
原理弄清楚了,顺便总结了几点Synchronized和ReentrantLock的区别:
ReentrantLock#isLocked
判断;ReentrantLock#lockInterruptibly
方法是可以被中断的;End
总的来说Synchronized的重量级锁和ReentrantLock的实现上还是有很多相似的,包括其数据结构、挂起线程方式等等。在日常使用中,如无特殊要求用Synchronized就够了。你深入了解这两者其中一个的实现,了解另外一个或其他锁机制都比较容易,这也是我们常说的技术上的相通性。