锁升级机制详解

锁是什么

锁是保证并发操作数据安全,防止数据不一致的工具。
使用synchronized关键字修饰的方法,默认情况下,JVM会为这个方法创建一个锁。

关于synchronized的三种锁机制

一共有3种锁:

  • 偏向锁
  • 轻量级锁
  • 重量级锁

为什么要有锁升级机制?

其实锁升级机制的核心思想就是为了减少获得锁和释放锁带来的性能开销,JVM才对锁进行了分级优化。目的就是在不牺牲性能的前提下保证线程安全

偏向锁

就像你长期租用了一个固定的柜子,柜子上贴上了“老王专用”,大部分时间只有你来用。管理员(JVM)看到是你的名字就直接让你用了,省去了开锁的流程。
核心场景主要就是优化了绝大多数情况下,锁只被一个线程访问的场景

轻量级锁

你今天准备来开你的柜子,但是发现小李准备用你的柜子(发生竞争)。但是小李可能就是过来试一下能不能开开所,或者用一下就走。这个时候你俩就会进行沟通
(比如CAS自旋)。避免惊动JVM。
核心场景主要是优化了锁竞争程度很低,或基本不竞争(交替运行),且线程持有时间很短的场景。

重量级锁

如果捏柜子变得很抢手,还有更多的人都要围过来用你的柜子(激烈竞争)。这个时候简单的沟通就没有用了,JVM就会出手,让所有的线程排好队,
并让来的晚的(没抢到的)的人去休息区阻塞等待,等柜子空出来再一个个叫醒。

以上场景即为锁升级机制的背景,接下来我们来讲一下底层原理

偏向锁原理

他假设:锁不仅不存在多线程竞争,而且总是由一个线程获得。
JVM会在对象头的Mark Word种记录获取到锁的线程id,以后这个线程再进入同步代码块时,不需要进行任何CAS操作来加锁解锁,只需要简单检查一下线程id是否是自己
即可,所以性能开销极低。(JDK15后被废弃)

轻量级锁原理

当偏向锁被另一个线程访问时(发生竞争),偏向锁就会撤销,并升级为轻量级锁。
他并不是直接把线程挂起,而是通过CAS自旋的方式尝试获取锁。线程会在自己的栈中创建一个叫锁记录的空间,然后通过CAS操作尝试将对象头中
的Mark Word中的线程id修改为指向自己的锁记录的指针。如果成功,则获取锁。

注:其实对于锁持有时间非常短的场景,让线程自旋尝试比直接挂起再唤醒的性能开销要小得多。因为挂起和唤醒线程需要从用户态切换到内核态,而自旋则不需要。
但是如果自旋时间过长或者自旋次数过多,就会自动升级为重量级锁。

重量级锁原理

重量级锁其实就是传统的synchronized实现,依赖于操作系统底层的互斥量
未抢到锁的线程会被阻塞,并放入一个等待队列中。当锁被释放时,操作系统会负责唤醒等待队列中的线程来重新竞争锁。
这个时候对象头的Mark Word会指向一个重量级锁的监视器对象而不是某个线程id了

再高并发的场景下,竞争很激烈,轻量级锁的CAS自旋会大量浪费CPU资源,这个时候就不如将线程挂起,让出CPU给其他线程使用。

其他注意的点

  1. 锁会降级?

锁升级是不可逆的

  1. 轻量级锁一定比重量级锁快?

取决于场景。如果锁竞争激烈的情况下,将线程挂起后,将CPU用于其他线程的运行,那么重量级锁会比轻量级锁性能更优。