synchronized 底层实现原理是什么?什么是锁的升级, 降级?

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

synchronized 的底层是由一对 monitorenter/monitorexit 指令实现的,Monitor 对象是同步的基本实现单元。

Java 6 之前,Monitor 的实现完全是依靠操作系统内部的互斥锁来实现的,这种机制需要进行用户态到内核态的切换,所以在 Java 6 之前,同步都是无差别的重量级操作。

之后的 jdk 中做了优化,提供了三种不同的 Monitor 实现,分别是:

  • 1.偏斜锁 (Biased Locking)

  • 2.轻量级锁

  • 3.重量级锁

所谓锁的升级,降级,实际上是 JVM 对 synchronized 优化的一种策略,JVM 会检测不同的竞争状态,然后自动切换到合适的锁实现,这种切换就是锁的升级,降级。

当没有出现锁的竞争时,默认使用的是偏斜锁。JVM 会利用 CAS 操作,在对象的头上的 Mark Word 部分设置线程 ID, 用来表示当前对象偏向于当前线程,所以并不涉及真正的互斥锁。这种策略是基于现实很多应用场景中,大部分对象生命周期最多会被一个线程锁定,使用偏斜锁可以降低无竞争锁的开销。

如果有另一个线程试图锁定某个以被加持偏斜锁的对象时,JVM 就需要撤销偏斜锁,并切换到轻量级锁实现。轻量级锁依赖 CAS 操作 Mark World 来试图获取锁,如果获取成功,就使用轻量级锁,否者,进一步升级到重量级锁。

题外话 1:网上有很多观点这样说,Java 不会进行锁的降级。

这种观点实际上错误的,锁的降级确实会发生。当 JVM 进入安全点时,会检查是否有闲置的 Monitor, 让后试图进行锁的降级。


补充知识点:

自旋锁

何为自旋锁?竞争锁失败的线程,并不会真实的在操作系统层面被挂起,JVM 会让线程做几个空循环(基于预测在短时间内能够获取到锁),经过若干次循环后,如果可以获取到锁,那么会进入到临界区,如果还不能获取到锁,才会真实在操作系统层面被挂起。

适用场景:

自旋锁可以减少线程的阻塞,这对于锁竞争不是很激烈,且占用锁时间非常短的代码来说,将会带来很大的性能提升,因为自旋的消耗会小于线程被挂起带来的消耗。

但是如果锁竞争激烈,或者持有锁的线程运行时间很长,就不适用于使用自旋锁了,应为自旋锁在获取到锁前一直都是占用 CPU 做无用功,线程自旋的消耗就大于线程被挂起带来的消耗了,造成 CPU 的浪费。

总结:自旋锁实际上是对乐观情况的优化。