轻量级锁、重量级锁都是啥玩意

大纲

轻量级锁、重量级锁都是啥玩意_第1张图片

前言


我的所有文章同步更新与Github–Java-Notes,想了解JVM(基本更完),HashMap源码分析,spring相关,,并发,剑指offer题解(Java版),可以点个star。可以看我的github主页,每天都在更新哟。

邀请您跟我一同完成 repo


在JDK1.6以后,为了减少消耗,锁进行了很多的升级。并且有了四种状态,从低到高

  • 无锁状态
  • 偏向锁状态
  • 轻量级锁状态
  • 重量级锁状态

下面就介绍一下这四种不同等级的锁

不同等级间的锁可以进行升级,但是不能进行降级

对象内存布局

在讲锁之前我们应该了解对象的内存布局,因为后面锁的判定时需要用到,

该内容可以看我的这篇文章,对象的内存布局,重点看对象头中的运行时数据(Mark Word)

看了这个你应该知道,Mark Word(以后简称MW) 会随着 标志位的变化而变化

MW变化

先把他放出来,免得等会儿你自己翻

轻量级锁、重量级锁都是啥玩意_第2张图片

无锁状态

轻量级锁、重量级锁都是啥玩意_第3张图片

可以看到,这种情况下,MW被分为了四个部分,他锁的标志位为 01,是否是偏向锁标志位为 0.

偏向锁(biasedLock)

先看MW变化(在对象头变化的那一张图片中)

可以看到,他的锁标志位无锁的标志位是一样的 都是 01,但是是否是偏向锁的标志位就变了 变成了 1.并且整个MW部分变成了5部分

目的

经过研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁代价更低而引人了偏向锁

偏向锁,就如同他的名字一样,“偏向”,“偏心”,英文"biased"也是偏爱的意思。

他为啥是叫这个呢,因为这个锁偏向于第一个获取到他的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要再进行同步

步骤

轻量级锁、重量级锁都是啥玩意_第4张图片

上面一共被分为三大部分,

  • 无锁状态
  • 偏向锁状态
  • 撤销偏向锁

上面的图中,线程一演示了偏向锁的初始化流程,线程2演示了偏向锁撤销的过程

初始化过程

  • 当锁对象第一次被线程获取的时候,虚拟机会将对象头中的锁标志位置为 “01”(偏向模式)
  • 同时,使用CAS(如果不了解CAS,可以看这篇文章,悲观锁和乐观锁)操作,把获取到这个锁的线程的ID记录在对象的MW中,
  • 如果 CAS成功,持有偏向锁的线程每次进入这个锁相关的同步块时,虚拟机可以不进行任何同步操作

撤销过程

  • 首先暂停拥有偏向锁的线程
  • 然后检查持有偏向锁的线程是否活着
    • 不活跃,将对象头设置成无锁状态 (标志位"01",但不可偏向)
    • 活,
      • CAS成功,重新偏向,更改线程ID
      • 失败,恢复成无锁状态,或者变成轻量级锁定状态。

轻量级锁

标志位为"00",可以看最开始的图

为啥叫轻量级锁,因为这是相比于传统的重量级锁而言,原来传统的重量级锁,使用的是系统互斥量实现的

他的出现并不是代替重量级锁,而是在没有多线程竞争的前提下,减少系统互斥量操作产生的性能消耗

步骤

先看图

轻量级锁、重量级锁都是啥玩意_第5张图片

加锁

  • 线程在执行同步块之前,JVM会现在当前线程的栈帧中创建用于存储锁记录(下图的LockRecord)的空间,并将对象头中的MW复制到锁记录中,官方称为 Displaced Mark Word
  • 然后,虚拟机将使用CAS操作,将对象的MW更新为指向锁记录的指针
    • 如果这个操作成功,那么该线程就有了该对象的锁,并且对象的MW的锁标志位置为 “00”,表示该对象处于轻量级锁定状态
    • 如果更新失败,表示其他线程竞争锁,当前线程尝试使用自旋来获取锁

轻量级锁、重量级锁都是啥玩意_第6张图片

解锁

  • 使用CAS操作将 Displaced Mark Word 替换回到对象头
    • 如果成功,则说明没有发生竞争
    • 失败,则表示当前锁存在竞争,锁就会膨胀成重量级锁
      • 释放锁,并且唤醒等待的线程

缺点

轻量级能提升程序同步性能的依据是"对于绝大部分的锁,在整个同步周期内都是不存在竞争的",这是一个经验数据。

  • 如果没有竞争,轻量级锁使用CAS操作,避免使用互斥量
  • 如果存在竞争,除了互斥量的开销,还有 CAS的操作,不仅没有提升,反而性能会下降

各个锁之间的转换

轻量级锁、重量级锁都是啥玩意_第7张图片

对比

优点 缺点 适用场景
偏向锁 加锁和解锁都不需要额外的消耗,和执行非同步方法相比仅存在纳秒级的差距 如果线程间存在锁竞争,会带来额外的锁撤销的消耗 只有一个线程访问同步块
轻量级锁 竞争的线程不会阻塞,提高了程序的响应速度 如果始终得不到锁竞争的线程,使用自旋会消耗CPU 追求响应时间
同步块执行速度非常块
重量级锁 线程竞争不使用自旋,不会消耗CPU 线程阻塞,响应时间慢 追求吞吐量
同步块执行时间较长

你可能感兴趣的:(Java,JVM,并发)