Java中的锁升级机制:偏向锁、轻量级锁和重量级锁
创始人
2025-07-14 15:30:39
0

Monitor实现的锁属于重量级锁,你了解过锁升级吗?

前面我们说了  synchronized 底层由monitor实现的,它那 synchronized 到底锁的是什么呢?随着 JDK 版本的升级,synchronized 又做出了哪些改变呢?“synchronized 性能很差”的谣言真的存在吗?

在介绍以上内容之前,我们要先知道重量级锁概念。

重量级锁

当另外一个线程执行到同步块的时候,由于它没有对应 monitor 的所有权,就会被阻塞,此时控制权只能交给操作系统,也就会从 user mode 切换到 kernel mode, 由操作系统来负责线程间的调度和线程的状态变更, 这就需要频繁的在这两个模式下切换(上下文转换)。有点竞争就找内核的行为很不好,会引起很大的开销,所以大家都叫它重量级锁,自然效率也很低,这也就给很多小伙伴留下了一个印象 —— synchronized 关键字相比于其他同步机制性能不好,但其实不然。

  • Monitor实现的锁属于重量级锁,里面涉及到了用户态和内核态的切换、进程的上下文切换,成本较高,性能比较低。
  • 在JDK 1.6引入了两种新型锁机制:偏向锁和轻量级锁,它们的引入是为了解决在没有多线程竞争或基本没有竞争的场景下因使用传统锁机制带来的性能开销问题。

一、MarkWord

在JVM虚拟机中,对象在内存中存储的布局可分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充。

图片图片

我们需要重点分析MarkWord对象头,因为Markword 是保存锁状态的关键,对象锁状态可以从偏向锁升级到轻量级锁,再升级到重量级锁,加上初始的无锁状态,可以理解为有 4 种状态。想在一个对象中表示这么多信息自然就要用位来存储。

图片图片

  • hashcode:25位的对象标识Hash码
  • age:对象分代年龄占4位
  • biased_lock:偏向锁标识,占1位 ,0表示没有开始偏向锁,1表示开启了偏向锁

thread:持有偏向锁的线程ID,占23位

  • epoch:偏向时间戳,占2位
  • ptr_to_lock_record:轻量级锁状态下,指向栈中锁记录的指针,占30位
  • ptr_to_heavyweight_monitor:重量级锁状态下,指向对象监视器Monitor的指针,占30位

我们可以通过lock的标识,来判断是哪一种锁的等级

  • 后三位是001表示无锁
  • 后三位是101表示偏向锁
  • 后两位是00表示轻量级锁
  • 后两位是10表示重量级锁

二、轻量级锁

在很多的情况下,在Java程序运行时,同步块中的代码都是不存在竞争的,不同的线程交替的执行同步块中的代码。这种情况下,用重量级锁是没必要的。因此JVM引入了轻量级锁的概念。

如果 CPU 通过 CAS(后面会细讲,戳链接直达)就能处理好加锁/释放锁,这样就不会有上下文的切换。

但是当竞争很激烈,CAS 尝试再多也是浪费 CPU,权衡一下,不如升级成重量级锁,阻塞线程排队竞争,也就有了轻量级锁升级成重量级锁的过程。

图片图片

作为程序员的我们最喜欢用代码说话,贴心的 openjdk 官网提供了可以查看对象内存布局的工具 JOL (java object layout),我们直接通过 Maven 引入到项目中。


      org.openjdk.jol
      jol-core
      0.14
  
public class SyncSample {


    private static Object LOCK = new Object();

    public static void main(String[] args) {


        System.out.println("----------未进入同步块,MarkWord 为:----------");

        System.out.println(ClassLayout.parseInstance(LOCK).toPrintable());


        synchronized (LOCK) {
            System.out.println("----------进入同步块,MarkWord 为:----------");
            System.out.println(ClassLayout.parseInstance(LOCK).toPrintable());
        }
    }

}

图片图片

2.1 加锁流程

1.在线程栈中创建一个Lock Record,将其obj字段指向锁对象。

图片图片

2.通过CAS指令将Lock Record的地址存储在对象头的mark word中(数据进行交换),如果对象处于无锁状态则修改成功,代表该线程获得了轻量级锁。

图片图片

3.如果是当前线程已经持有该锁了,代表这是一次锁重入。设置Lock Record第一部分为null,起到了一个重入计数器的作用。

图片图片

4.如果CAS修改失败,说明发生了竞争,需要膨胀为重量级锁。

2.2 解锁流程

1.遍历线程栈,找到所有obj字段等于当前锁对象的Lock Record。

2.如果Lock Record的Mark Word为null,代表这是一次重入,将obj设置为null后continue。

图片图片

3.如果Lock Record的 Mark Word不为null,则利用CAS指令将对象头的mark word恢复成为无锁状态。如果失败则膨胀为重量级锁。

图片图片

三、偏向锁

轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有。

图片图片

可是多线程环境,也不可能只有同一个线程一直获取这个锁,其他线程也是要干活的,如果出现多个线程竞争的情况,就会有偏向锁升级的过程。

1.在线程栈中创建一个Lock Record,将其obj字段指向锁对象。

图片图片

2.通过CAS指令将Lock Record的线程id存储在对象头的mark word中,同时也设置偏向锁的标识为101,如果对象处于无锁状态则修改成功,代表该线程获得了偏向锁。

图片图片

3.如果是当前线程已经持有该锁了,代表这是一次锁重入。设置Lock Record第一部分为null,起到了一个重入计数器的作用。与轻量级锁不同的时,这里不会再次进行cas操作,只是判断对象头中的线程id是否是自己,因为缺少了cas操作,性能相对轻量级锁更好一些。

图片图片

思考:偏向锁可以绕过轻量级锁,直接升级到重量级锁吗?

图片

四、面试题

面试官:Monitor实现的锁属于重量级锁,你了解过锁升级吗?

Java中的synchronized有无锁(无锁就是没有对资源进行锁定,任何线程都可以尝试去修改它)、偏向锁、轻量级锁、重量级锁四种形式,偏向锁、轻量级锁、重量级锁分别对应了锁只被一个线程持有、不同线程交替持有锁、多线程竞争锁三种情况

锁别

描述

重量级锁

底层使用的Monitor实现,里面涉及到了用户态和内核态的切换、进程的上下文切换,成本较高,性能比较低。

轻量级锁

线程加锁的时间是错开的(也就是没有竞争),可以使用轻量级锁来优化。轻量级修改了对象头的锁标志,相对重量级锁性能提升很多。每次修改都是CAS操作,保证原子性

偏向锁

一段很长的时间内都只被一个线程使用锁,可以使用了偏向锁,在第一次获得锁时,会有一个CAS操作,之后该线程再获取锁,只需要判断mark  word中是否是自己的线程id即可,而不是开销相对较大的CAS命令


相关内容

热门资讯

如何允许远程连接到MySQL数... [[277004]]【51CTO.com快译】默认情况下,MySQL服务器仅侦听来自localhos...
如何利用交换机和端口设置来管理... 在网络管理中,总是有些人让管理员头疼。下面我们就将介绍一下一个网管员利用交换机以及端口设置等来进行D...
施耐德电气数据中心整体解决方案... 近日,全球能效管理专家施耐德电气正式启动大型体验活动“能效中国行——2012卡车巡展”,作为该活动的...
Windows恶意软件20年“... 在Windows的早期年代,病毒游走于系统之间,偶尔删除文件(但被删除的文件几乎都是可恢复的),并弹...
20个非常棒的扁平设计免费资源 Apple设备的平面图标PSD免费平板UI 平板UI套件24平图标Freen平板UI套件PSD径向平...
德国电信门户网站可实时显示全球... 德国电信周三推出一个门户网站,直观地实时提供其安装在全球各地的传感器网络检测到的网络攻击状况。该网站...
为啥国人偏爱 Mybatis,... 关于 SQL 和 ORM 的争论,永远都不会终止,我也一直在思考这个问题。昨天又跟群里的小伙伴进行...
《非诚勿扰》红人闫凤娇被曝厕所... 【51CTO.com 综合消息360安全专家提醒说,“闫凤娇”、“非诚勿扰”已经被黑客盯上成为了“木...