Java并发之锁升级:无锁>偏向锁>轻量级锁>重量级锁

Java并发之锁升级:无锁->偏向锁->轻量级锁->重量级锁

对象头markword

在lock_bits为01的大前提下,只有当是否偏向锁位值为1的时候,才表明当前对象处于偏向锁定状态;为0时,表明该对象被hash计算了

如果线程获得该偏向锁,则对象头是否偏向锁置1,标志位为01。

锁的升级过程——无锁->偏向锁->轻量级锁->重量级锁

明确 Java 线程切换的代价,是理解java中各种锁的优缺点的基础之一。

JDK1.6以前synchronized 关键字只表示重量级锁,1.6之后区分为偏向锁、轻量级锁、重量级锁。

偏向锁,轻量级锁,重量级锁分别解决三个问题

  • 只有一个线程进入临界区
  • 多个线程交替进入临界区
  • 多个线程同时进入临界区

偏向锁:

为什么引入偏向锁

​ HotSpot的作者大量的研究发现,大多数时候是不存在锁竞争的,常常是一个线程多次获得同一个锁,因此如果每次都要竞争锁会增大很多没有必要付出的代价,为了降低获取锁的代价,才引入的偏向锁。

​ Synchronized 是通过对象内部的一个叫做监视器锁(monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的 Mutex Lock 来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么 Synchronized 效率低的原因。因此,这种依赖于操作系统 Mutex Lock 所实现的锁我们称之为“重量级锁”。JDK 中对 Synchronized 做的种种优化,其核心都是为了减少这种重量级锁的使用。JDK1.6 以后,为了减少获得锁和释放锁所带来的性能消耗,提高性能,引入了自旋锁、自适应自旋锁、轻量级锁和偏向锁。

轻量级锁

轻量级锁
轻量级是相对于使用操作系统互斥量来实现的传统锁而言的。但是,首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。在解释轻量级锁的执行过程之前,先明白一点,轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。

重量级锁

​ 内置锁在Java中被抽象为监视器锁(monitor)。在JDK 1.6之前,监视器锁可以认为直接对应底层操作系统中的互斥量(mutex)。这种同步方式的成本非常高,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。因此,后来称这种锁为“重量级锁”。

无锁->偏向锁

​ 当一个线程访问同步块并 获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出 同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否 存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。如果测试失败,则需 要再测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):如果没有设置,则 使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。

​ 偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时, 持有偏向锁的线程才会释放锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有正在执行的字节码)。它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着, 如果线程不处于活动状态,则将对象头设置成无锁状态;如果线程仍然活着,拥有偏向锁的栈 会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word要么重新偏向于其他 线程,要么恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。

  • 当没有竞争出现时,默认会使用偏向锁。JVM 会利用 CAS 操作(compare and swap),在对象头上的 Mark Word 部分设置线程 ID,以表示这个对象偏向于当前线程,所以并不涉及真正的互斥锁。这样做的假设是基于在很多应用场景中,大部分对象生命周期中最多会被一个线程锁定,使用偏向锁可以降低无竞争开销。
  • 如果有另外的线程试图锁定某个已经被偏向过的对象,JVM 就需要撤销(revoke)偏向锁,并切换到轻量级锁实现。轻量级锁依赖 CAS 操作 Mark Word 来试图获取锁,如果重试成功,就使用轻量级锁;否则,进一步升级为重量级锁

偏向锁的获得和撤销流程

轻量级锁膨胀

​ 线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并 将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用 CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。轻量级锁解锁 轻量级解锁时,会使用原子的CAS操作将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败表示当前锁存在竞争,锁就会膨胀成重量级锁。

总结

可以理解为:

当只有一个线程时,线程占有锁时是偏向锁;当有其他线程竞争锁时,偏向锁升级为轻量级锁;当多线程不能交替获得轻量级锁而不产生竞争(可以自旋等待)时,轻量级锁升级为重量级锁。

参考:

原文地址:https://www.cnblogs.com/aslanvon/p/15072125.html