目录

一 重量级锁

1.1 什么是重量级锁?重量级是怎么体现的?

1.2 重量级锁的监视器工作流程

1.2.1 线程竞争锁

1.2.2 线程锁竞争成功

1.2.3 线程竞争锁失败

1.2.4 运行中的线程调用wait方法后,会被挂起

1.2.5 阻塞队列中线程时间到期或者被唤醒

1.2.6 轻量级锁膨胀之后,会导致竞争的线程加入到竞争失败队列_cxq中,然后挂起线程

1.3 重量级锁的膨胀流程

1.4 重量级锁加锁流程

1.5重量级锁释放锁的流程

1.6 重量级锁是如何保存hashcode和分代信息的

1.7 重量级锁是如何实现重入的

二 轻量级锁

2.1 什么是轻量级锁?轻量是怎么体现的?

2.2 轻量级锁的工作流程

2.2.1 锁对象处于无锁状态

2.2.2 锁对象处于有锁状态,但是是线程自己(重入)

2.2.3 锁对象处于有锁状态,但是是别的线程持有锁(非重入)

2.3 轻量级锁加锁流程

2.4 轻量级锁释放锁的流程

2.5 轻量级锁升级为重量级锁的流程

2.5.1 升级时机

2.5.2 升级流程

2.6 轻量级锁如何保存锁对象的分代信息和hashcode等信息

2.7 轻量级锁重入是怎么实现的

三 偏向锁

3.1 什么是偏向锁?

3.2 偏向锁加锁流程

3.2.1 没有开启偏向

3.2.2 无锁状态

3.2.3 重入状态

3.2.4 已经偏向其他线程

3.3 偏向锁释放锁的流程

3.4 偏向锁升级流程

3.4.1 升级时机

3.4.2 升级流程

3.5 偏向锁如何保存hashcode、分代信息等?

一 重量级锁

1.1 什么是重量级锁?重量级是怎么体现的?

第一:执行同步代码块的时候或者执行同步方法的时候,需要为锁对象创建监视器,监视器用于关联锁对象、以及锁对象的原始头信息、重入次数、竞争失败队列、竞争队列和阻塞队列等信息,所以保存的东西占用的内存量很多,尤其是线程并发量大的时候。

第二:另外线程竞争失败的队列需要进入队列挂起或者线程阻塞也需要挂起,当挂起的时间到期又需要唤醒线程,等待和唤醒是属于系统调用,会涉及到CPU在用户态和内核态切换,线程太多就会频繁切换。

第三:内核线程需要操作系统进行调度,所以也会耗费时间。

综上所述:上面就是重量级锁也是体现重量级的地方。

1.2 重量级锁的监视器工作流程

1.2.1 线程竞争锁

新到来的线程和释放锁唤醒的线程(假定继承人)会同时通过CAS方式竞争锁,即将监视器中的线程持有者_owner置为自己

1.2.2 线程锁竞争成功

线程竞争锁成功,就可以进入临界区执行代码

1.2.3 线程竞争锁失败

根据不同的唤醒策略QMode,那么失败后的处理结果不一样:

情况一:QMode= 2

失败的线程封装成ObjectWaiter,然后放入到_cxq锁竞争失败队列,并且唤醒队首的线程(此时唤醒的线程就是假定继承者),然后返回。

情况二:QMode = 3

失败的线程封装成ObjectWaiter,然后放入到_cxq锁竞争失败队列,并且将_cxq队列中所有元素插入到_EntryList队尾,并且唤醒_EntryList队首线程(此时唤醒的线程就是假定继承者)。

情况三:QMode = 4

失败的线程封装成ObjectWaiter,然后放入到_cxq锁竞争失败队列,并且将_cxq队列中所有元素插入到_EntryList队首,并且唤醒_EntryList队首线程(此时唤醒的线程就是假定继承者)。

1.2.4 运行中的线程调用wait方法后,会被挂起

运行中的线程调用wait方法后,会被挂起,放入到阻塞队列中_WaitSet中

1.2.5 阻塞队列中线程时间到期或者被唤醒

阻塞队列_WaitSet中线程时间到期或者被唤醒(notify),不会立刻抢占锁,而是先被放入到_EntryList中或者_cxq队列中(取决于Knob_MoveNotifyee这个值)

1.2.6 轻量级锁膨胀之后,会导致竞争的线程加入到竞争失败队列_cxq中,然后挂起线程

1.3 重量级锁的膨胀流程

第一:获取锁对象的mark word

第二:如果锁状态已经是重量级锁,返回监视器

第三:如果当前锁正处于膨胀中,说明有其他线程正在膨胀锁,则当前线程自旋,重复步骤一

第四:轻量级锁线程膨胀,首先创建监视器和初始化;然后CAS方式将锁状态置为膨胀中,如果失败有其他线程竞争,则释放锁然后重试,重复步骤一;紧接着设置监视器的displace header,将锁的持有者指向轻量级锁的指向的lock record,并且将锁对象的指针指向监视器(此步骤无需CAS,因为处于膨胀中无其他线程打扰),然后返回监视器

第五:如果是其他情况,则也会创建监视器和初始化,并且将锁对象的指针以CAS方式指向监视器,如果失败则释放锁并进行重试,然后返回监视器。但是锁的持有者是NULL,即没有持有者。

1.4 重量级锁加锁流程

第一:不管是重量级锁获取锁还是轻量级锁锁竞争锁失败导致的升级或者轻量级锁因为升级而导致重入必须获取重量级锁,都需要先走膨胀流程,再获取锁

第二:当前锁对象处于无锁状态,则将监视器的_owner字段通过CAS方式设置为当前线程,设置成功,返回执行同步代码块

第三:如果是锁重入的情况,那么递增_recursions,返回执行同步代码块

第四:如果当前线程是之前持有轻量锁的线程,可能发生重入。当重入之后,发现已经不是轻量级锁,则会走轻量级锁膨胀流程,然后进入加锁流程,重入次数只为我1,然后将锁的持有者_owner置为当前线程(锁升级的时候_owner是指向BasicLock(Lock Record)的指针),然后返回

第五:如果当前没有锁,则自旋,自旋次数,默认10次。自旋期间,获取到锁了,则返回;如果没有则再次尝试获取锁,获取到锁了,则返回,如果失败则再次尝试自旋。

第六:如果自旋成功,返回;如果继续失败,则将当前线程封装成Node节点,然后加入到_cxq等待队列。如果加入成功,则挂起;如果加入失败,则无限制获取锁直到成功,没有次数限制。

1.5重量级锁释放锁的流程

第一:不管是重量级锁还是轻量级锁因为锁升级的缘故,都需要走膨胀流程,然后再释放锁

第二:锁如果没有膨胀,则将锁置为膨胀中;如果锁为膨胀中则自旋等待膨胀完成;如果以膨胀则返回

第三:一般情况下,锁的持有者和当前线程是一致的,但是轻量级锁有可能不一样。

因为轻量级锁升级的时候,锁的持有者无法设置为锁升级的线程,只能设置为当时持有轻量锁的线程的lock record。所以当持有轻量级锁的线程释放锁的时候,因为锁升级导致释放失败,从而会进入重量级锁的释放流程,所以导致释放锁的线程可能并不是重量级锁的持有者_owner。

此时的轻量级锁已经是重量级锁,所以释放锁需要修改的是重量级锁的信息。故将重入次数_recursions置为0,将线程所有者_owner置为自己

第四: 如果是重量级锁重入,那么递减_recursions就行

第五:锁释放完毕,需要根据QMode唤醒_cxq或者_EntryList队首的线程作为假定继承人,假定继承人需要和新到来的线程竞争锁,由于重量级并不是公平锁,所以并不一定会成功获取锁

失败的线程封装成ObjectWaiter,然后放入到_cxq锁竞争失败队列,并且唤醒队首的线程(此时唤醒的线程就是假定继承者),然后返回。

情况二:QMode = 3

失败的线程封装成ObjectWaiter,然后放入到_cxq锁竞争失败队列,并且将_cxq队列中所有元素插入到_EntryList队尾,并且唤醒_EntryList队首线程(此时唤醒的线程就是假定继承者)。

情况三:QMode = 4

失败的线程封装成ObjectWaiter,然后放入到_cxq锁竞争失败队列,并且将_cxq队列中所有元素插入到_EntryList队首,并且唤醒_EntryList队首线程(此时唤醒的线程就是假定继承者)。

1.6 重量级锁是如何保存hashcode和分代信息的

重量级锁监视器有一个_header字段,用于保存锁对象原始的hashcode或者分代信息

1.7 重量级锁是如何实现重入的

重量级锁监视器有一个_recursions字段,用于记录重入次数,如果没有重入了

_recursions = 0;如果发生重入,_recursions递增

二 轻量级锁

2.1 什么是轻量级锁?轻量是怎么体现的?

当只有少量线程的时候,并发程度很低,不存在同时竞争锁的情况,只需要尝试将自己的线程栈帧中的锁记录信息设置到锁对象中就可以,不需要进行等待和唤醒等操作,所以不存在内核态和用户态的切换,只有系统调用才会发生内核态和用户态的切换;而且还少了操作系统调度,提升了性能。所以它是轻量级的。

注意:轻量级锁获取锁的过程并不存在自旋的情况,只是获取锁失败,在膨胀为重量级锁的时候如果有其他线程正在膨胀,则膨胀失败,进行自旋重试,而且这里的自旋也并没有次数限制。重量级锁是有自旋的,但是前提是轻量级锁已经膨胀为重量级锁。

2.2 轻量级锁的工作流程

2.2.1 锁对象处于无锁状态

第一:从线程栈帧中找到一个可用的BasicObjectLock,即锁记录lock record,然后将锁记录obj和锁对象关联

第二:将线程的锁记录中BasicLock锁的displaced header设置为现在无状态的锁对象的mark word,保存锁记录原始信息,比如hashcode、分代信息等

第三:尝试将锁对象的mark word中的指针指向锁记录的BasicLock的指针,这样线程就持有轻量级锁,如果设置失败走膨胀流程。

2.2.2 锁对象处于有锁状态,但是是线程自己(重入)

如果持有锁的线程是线程自己,那么就是重入锁,将锁记录的displaced header置为NULL就可以,这里的重入获取的是一个新的BasicObjectLock,之所以不用保存锁对象的mark word,是因为第一个持有锁的记录已经保存了。

2.2.3 锁对象处于有锁状态,但是是别的线程持有锁(非重入)

如果既不是无锁状态,也不是重入,那么就表示有线程竞争,此时应该升级轻量级锁,走轻量级锁膨胀流程。

2.3 轻量级锁加锁流程

第一:获取锁对象的mark word

第二:如果锁对象是无锁状态

首先,将mark word设置到锁记录BasicLock中的displaced header中;然后通过CAS方式将锁对象的锁记录指针指向当前线程栈帧中的BasicLock锁记录地址;最后获取成功,执行同步块中代码

第三:如果是重入

如果是重入,那么当前栈帧会分配一个锁记录BasicObjectLock,然后将锁记录的displaced header置为NULL,设置成功,执行同步块中代码

第四:锁对象是无锁状态,但是设置锁记录指针失败或者锁对象被其他线程持有,此时需要升级轻量级锁,进入膨胀流程

2.4 轻量级锁释放锁的流程

第一:获取锁对象的mark word

第二:遍历线程帧栈中所有锁记录,获取保存有锁对象mark word的锁记录,然后将锁记录关联的锁对象置为NULL,表示已经移除了锁

第三:如果是轻量级重入锁,和偏向锁一样,那么删除锁记录即可

第四:如果不是重入锁,那就需要将锁记录关联的displaced header替换回mark word。替换成功,释放锁成功;释放失败,表示锁对象已经被别的线程膨胀了,现在的锁对象指向的是ObjectMonitor的地址,所以此时需要走重量级锁的退出流程

2.5 轻量级锁升级为重量级锁的流程

2.5.1 升级时机

只要发生轻量级锁竞争,就会升级为重量级锁

2.5.2 升级流程

第一:线程竞争轻量级锁失败,表示现在有其他线程持有轻量级锁

第二:此时会走重量级锁的膨胀流程

第三:获取锁对象,检查对象状态

#1 如果锁对象已经有监视器,说明已经是重量级锁,直接返回监视器

#2 如果锁对象正处于膨胀中,说明有其他线程在膨胀,那么当前线程自旋,直到该锁对象膨胀成功

#3 如果是轻量级线程触发膨胀,首先,创建所对象监视器;然后初始化监视器再然后将锁对象状态置为膨胀中,如果失败,说明有其他线程已经触发了膨胀流程

#4 如果锁对象置为膨胀状态成功,则获取锁对象的mark word,然后设置到监视器、将轻量级锁的线程持有者置为_owner(注意不是膨胀线程)、设置监视器锁对象,然后返回监视器

第四:然后进入到升级后的流程,主要区别为之前触发膨胀的线程和之前持有轻量级锁的线程

触发膨胀的线程流程:

#1 获取当前膨胀的线程

#2 当前线程既不是锁的持有者(锁的持有者是之前持有轻量级锁的线程),也不是重入线程,则尝试自旋,这期间可能持有锁的线程已经释放了,如果自旋成功获取到锁则返回执行同步块代码。默认情况下自旋只有10次。

#3 如果自旋失败,则尝试再次获取锁,如果成功获取到锁则返回执行同步块代码;如果失败再次尝试自旋,如果自旋成功,返回

#4 将线程封装成Node节点,然后以CAS方式设置到_cxq队列中,将该线程挂起;如果设置失败,则尝试再次获取锁,如果成功返回,如果失败则自旋,重新将Node节点再次放入到_cxq竞争失败队列中,如此往复,没有限制

膨胀前持有锁的线程的流程:

#1因为锁对象mark word被改变了导致释放锁失败,从而进入轻量级锁膨胀流程,再进行释放锁

#2 膨胀完成后,需要在进行重量级锁的释放,因为此时的锁已经是重量级锁。

#3 如果当前线程之前之前持有轻量级锁的线程,首先将锁的持有者_owner设置为当前线程,不再是之前的该线程的帧栈中的BasicLock指针,然后重入次数_recursions置为0

#4 然后将锁的_owner置为NULL,表示当前线程释放锁,然后根据具体的唤醒策略,从_cxq或者_EntryList唤醒一个线程去作为假定继承人

2.6 轻量级锁如何保存锁对象的分代信息和hashcode等信息

轻量级锁是可以将锁对象的分代信息和hashcode保存在线程帧栈中的displaced headere,当释放锁的时候就会替换回去

2.7 轻量级锁重入是怎么实现的

第一:如果有线程竞争锁,每一个线程都有会分配BasicObjectLock,字段obj关联

锁对象,lock关联BasicLock,而BasicLock的_displaced_header保存锁对象的mark word

第二:当线程重入的时候,同样会分配一个BasicObjectLock,也会将obj字段关联锁对象,但是不会设置BasicLock的_displaced_header,因为只需要有一个BasicObjectLock记录就可以了。

第三:在释放轻量级锁的时候,一般会遍历线程中所有的BasicObjectLock锁记录,然后先从低位开始逐渐遍历和释放。

三 偏向锁

3.1 什么是偏向锁?

当经常只有一个线程执行同步代码块的时候,其实复制mark word到锁记录displaced mark word都是没有必要的, 只要在锁对象中设置一个线程标识,每一次校验线程ID是不是持有锁的线程ID, 如果持有则可以执行同步代码块,没有多余的其他操作。这个偏向于某一线程的锁,就叫做偏向锁。

3.2 偏向锁加锁流程

3.2.1 没有开启偏向

直接升级为轻量级锁,走轻量级锁升级流程

3.2.2 无锁状态

创建无锁状态的mark word,然后当前线程ID创建偏向锁的mark word,然后用D创建的偏向当前线程ID的偏向锁的mark word更新锁对象,如果个成功表示已经获取偏向锁;如果失败,表示有其他线程竞争偏向锁,需要撤销锁,升级为轻量级锁。

3.2.3 重入状态

如果已经存在偏向锁,但是偏向的是当前线程,则什么也不用做,可以直接执行同步块代码

3.2.4 已经偏向其他线程

则首先会走偏向锁撤销流程,至于是否升级为轻量级锁,需要取决于安全安全点的时候,持有偏向锁的线程是否已退出同步代码块

3.3 偏向锁释放锁的流程

第一:退出同步代码块的时候,需要释放锁

第二:获取锁对象,一个线程帧栈因为重入可能有多个锁记录BasicObjectLock,所以需要从遍历栈帧中的BasicObjectLock,找到对应的锁记录BasicObjectLock

第三:首先需要将BasicObjectLock锁记录的obj锁对象字段置为NULL,删除和锁对象的关联,即删除锁记录。这样就完成了锁的释放流程。

3.4 偏向锁升级流程

3.4.1 升级时机

只要发生线程竞争,就会撤销锁,然后走升级流程,除非发生重偏向

3.4.2 升级流程

第一:当调用锁对象的hashcode或者多线程竞争锁失败,此时都需要将偏向锁撤销,升级为轻量级锁

第二:首先需要撤销偏向锁,将锁对象置为无锁状态

第三:判断锁状态是否有锁,如果没有锁,则设置线程栈帧中的锁记录的displaced header为锁对象的mark word, 然后尝试将锁记录的指针更新到锁对象,让锁对象指向自己,如果成功,那么获取锁,执行同步代码块代码;如果失败表示有多个线程在竞争锁,进入轻量级锁膨胀流程

第四:如果有锁,但是锁对象mark word的指针指向的是自己,则属于重入,只需要将锁记录的displaced header置为NULL就可以

第五:如果是有锁,但是不是重入或者无锁撞他竞争锁失败,都会进入轻量级锁膨胀流程。

3.5 偏向锁如何保存hashcode、分代信息等?

偏向锁是无法保存hashcode、分代信息的,如果调用锁对象的hashcode方法,此时会撤销偏向锁,升级为轻量级锁。因为轻量级锁会将锁对象的hashcode和age等信息保存到线程帧栈中的displaced header.

2021-06-01 深入分析偏向锁、轻量级锁和重量级锁相关推荐

  1. 12.synchronized的锁重入、锁消除、锁升级原理?无锁、偏向锁、轻量级锁、自旋、重量级锁

    小陈:呼叫老王...... 老王:来了来了,小陈你准备好了吗?今天我们来讲synchronized的锁重入.锁优化.和锁升级的原理 小陈:早就准备好了,我现在都等不及了 老王:那就好,那我们废话不多说 ...

  2. 499、Java分布式和集群12 -【SpringCloud视图微服务 - 消息总线Bus】 2021.06.01

    目录 0.RabbitMQ 1.先运行,看到效果,再学习 2.pom.xml 3.bootstrap.yml 4.application.yml 5.ProductDataServiceApplica ...

  3. 《惢客创业日记》2021.06.01(周二)五月份的工作总结

    时间真快,五月一晃而过,感觉时间就像一辆没有停靠站台的疾驰列车,装载着每个人的经历和人生驶向没有尽头的远方.不管是对,还是错,是得到,还是失去,都将其存储到每个人的大脑记忆中,由此让我想到了一句话:& ...

  4. 简单理解重量级锁、轻量级锁、偏向锁

    全文使用synchronized来说明. synchronized给对象上锁,先上偏向锁,在上轻量级锁,最后上重量级锁.上什么锁,是gvm根据竞争程度自行变换的. 重量级锁 计算机操作系统本有Moni ...

  5. java多线程之锁 -- 偏向锁、轻量级锁、自旋锁、重量级锁

    转载至:https://blog.csdn.net/zqz_zqz/article/details/70233767 之前做过一个测试,详情见这篇文章<多线程 +1操作的几种实现方式,及效率对比 ...

  6. 1.6的锁优化(适应性自旋/锁粗化/锁削除/轻量级锁/偏向锁)

    高效并发是JDK 1.6的一个重要主题,HotSpot虚拟机开发团队在这个版本上花费了大量的精力去实现各种锁优化技术,如适应性自旋(Adaptive Spinning).锁削除(Lock Elimin ...

  7. Java如何避免重量级锁,Java 中锁是如何一步步膨胀的(偏向锁、轻量级锁、重量级锁)...

    文章目录 重量级锁(Mutex Lock) 偏向锁(比较 ThreadID) 偏向锁获取过程 偏向锁的释放 轻量级锁(自旋) 轻量级锁的加锁过程 轻量级锁的释放 总结 重量级锁(Mutex Lock) ...

  8. 操作系统锁的实现方法有哪几种_java 偏向锁、轻量级锁及重量级锁synchronized原理...

    Java对象头与Monitor java对象头是实现synchronized的锁对象的基础,synchronized使用的锁对象是存储在Java对象头里的. 对象头包含两部分:Mark Word 和 ...

  9. 轻量级锁_Java高级架构师-Java锁的升级策略 偏向锁 轻量级锁 重量级锁

    欢迎关注头条号:Java小野猫 这三种锁是指锁的状态,并且是专门针对Synchronized关键字.JDK 1.6 为了减少"重量级锁"的性能消耗,引入了"偏向锁&quo ...

最新文章

  1. ibm xml专区中对XPATH的一个好文
  2. 嵌入式linux启动过程分析,嵌入式Linux裸机开发(二)——S5PV210启动过程分析
  3. Xcode插件,模板安装
  4. spring-others
  5. 各种有用的东西留言板
  6. AS运行软件超时解决方法
  7. 苏宁买买买!将收购家乐福80%股份 成为家乐福中国控股股东
  8. Android代码里面设置字体颜色的三种方法
  9. WIN7通过mount挂载nfs配置root权限,解决不可写的问题
  10. 实现Springboot整合UReport2
  11. 计算机考研复试_数据库
  12. 系统操作手册_辽宁高考志愿填报系统2019操作手册(考生版)
  13. 阿里云服务器(Centos7)安装谷歌浏览器
  14. Python基础入门实验3附加题
  15. 计算机工作组环境和域环境的区别
  16. 放大器输入级的差动放大 + 电流镜
  17. 被手机“绑架”又不能完全指望手机的柔宇科技,上市之后会是一片坦途吗?
  18. vs2019社区版下载教程(详细)
  19. 张学友-歌神同行.叁(国语篇)2019【SACD-ISO】
  20. 淘宝店铺宝贝批量复制工具

热门文章

  1. Python利用双端队列判断回文词
  2. 校友会2019中国大学计算机,校友会2019中国大学一流专业排名800强出炉,北大清华复旦前三...
  3. 用php脚本获取服务内容,如何使用PHP脚本仅获取数据库的内容
  4. Evaluation or Assessment
  5. centos7源码安装ntp_如何安装和配置 Chrony 作为 NTP 客户端?
  6. python自动安装pip教程_谈谈全自动安装常使用的pip install的原理及作用!!!
  7. 鸿蒙os在3月底推送,华为鸿蒙OS Beta 3将从3月31日起推送
  8. android wifi ap sta,WIFI的AP/Sta模式简单介绍
  9. ajax 请求成功 再执行javascript,jquery中ajax请求后台数据成功后既不执行success也不执行error的完美解决方法...
  10. idea jdbc封装_IDEA 中 MyBatis还可以这么玩!!!