【Java对象头、锁机制】Java对象头、锁的类别以及锁的升级机制

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


一、Java的对象头

Java 多线程的锁都是基于对象的,Java 中的每一个对象都可以作为一个锁。我们常说的类锁其实也是对象锁。

Java 类只有一个 Class 对象,而 Class 对象也是特殊的 Java 对象。所以我们常说的类锁,其实就是 Class 对象的锁。

每个 Java 对象都有对象头。如果是非数组类型,则用 2 个字宽来存储对象头,如果是数组,则会用 3 个字宽来存储对象头。

长度 内容 解释
32/64位 Mark Word 包含对象的hashcode和锁的信息
32/64位 Class Metadata Address 指向类的元数据的指针
32/64位 length 若当前对象是数组,则代表数组长度,否则没有这个字段

二、锁的类别

锁的状态从低到高依次为无锁->偏向锁->轻量级锁->重量级锁,升级的过程就是从低到高,降级在一定条件也是有可能发生的。

1、偏向锁

大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得,于是引入了偏向锁。

当线程访问同步块获取锁时,会在对象头和栈帧中的锁记录里存储偏向锁的线程ID,之后这个线程再次进入同步块时都不需要CAS来加锁和解锁了,偏向锁会永远偏向第一个获得锁的线程,如果后续没有其他线程获得过这个锁,持有锁的线程就永远不需要进行同步,也就是说,偏向锁在资源无竞争情况下消除了同步语句,连 CAS 操作都不做了,提高了程序的运行性能。反之,当有其他线程竞争偏向锁时,持有偏向锁的线程就会释放偏向锁
在这里插入图片描述

2、轻量级锁

介绍轻量级锁前先介绍一下自旋锁。
自旋锁:由于大部分时候,锁被占用的时间很短,共享变量的锁定时间也很短,所有没有必要挂起线程,用户态和内核态的来回上下文切换严重影响性能。自旋的概念就是让线程执行一个忙循环,可以理解为就是啥也不干,防止从用户态转入内核态。说白了就是让线程不断去尝试获取锁,但是不进入挂起状态。
自旋是需要消耗 CPU 的,如果一直获取不到锁的话,那该线程就一直处在自旋状态,白白浪费 CPU 资源。但是 JDK 采用适应性自旋,简单来说就是线程如果自旋成功了,则下次自旋的次数会更多,如果自旋失败了,则自旋的次数就会减少。自旋的时间不是固定时间,而是由前一次在同一个锁上的自旋时间和锁的持有者状态来决定。
介绍完自旋锁,开始进入正题,轻量级锁:
代码进入同步块的时候,JVM将会使用CAS方式来尝试获取锁,如果更新成功则会把对象头中的状态位标记为轻量级锁,如果更新失败,当前线程就尝试自旋来获得锁。

在这里插入图片描述

3、重量级锁

重量级锁依赖于操作系统的互斥量(mutex) 实现的,而操作系统中线程间状态的转换需要相对比较长的时间,所以重量级锁效率很低,但被阻塞的线程不会消耗 CPU。这里就不多说了,大家可以自己C一下。

三、锁的升级机制

整个锁升级的过程非常复杂,我尽力去除一些无用的环节,简单来描述整个升级的机制。

(1)查看Mark Word 里面是不是放的自己的线程ID,如果是,表示当前线程是处于 “偏向锁” 。

(2)如果 Mark Word 不是自己的线程ID,锁升级,这时候,用 CAS 来执行切换,暂停持有偏向锁的线程。

(3)当前线程和暂停的线程分别创建锁记录空间,并将Mark Word复制到锁空间。用CAS修改Mark Word,将值替换为指向锁记录的指针(两个线程竞争)。

(4)第(3)步中成功执行 CAS 的线程获得资源,失败的线程则进入自旋 。

(5)自旋的线程在自旋过程中,如果成功获得资源(即之前获的资源的线程执行完成并释放了共享资源),则依然处于轻量级锁的状态;如果自旋失败,进入重量级锁的状态,这个时候,自旋的线程进行阻塞,等待之前线程执行完成并唤醒自己。

简单点说,偏向锁就是通过对象头的偏向线程ID来对比,成功后都不需要CAS了,失败则升级为轻量级锁。而轻量级锁主要就是通过CAS修改对象头锁记录和自旋来实现,自旋失败就继续升级。重量级锁则是除了拥有锁的线程其他全部阻塞。


此篇文章为学习笔记,是对别人知识的理解,加上自己的一些个人理解汇聚而成。若有侵权联系删除。写作不易,望好兄弟们来个三连支持。感激不尽
注:原文出处为https://mp.weixin.qq.com/s/-xFSHf7Gz3FUcafTJUIGWQ

猜你喜欢

转载自blog.csdn.net/qq_51720181/article/details/125586905