1.锁升级的4种状态:
无锁状态
偏向锁状态
轻量级锁状态
重量级锁状态
2.整体的流程图
3.详细介绍
偏向锁状态
原因:
很多时候,没有锁竞争。通常一个线程多次获得同一个锁,所以如果每次都要争锁,会增加很多不必要的成本。为了降低获取锁的成本,引入了偏置锁。
升级:
当线程1访问代码块并获取锁对象时,它会在java对象头和堆栈帧中记录有偏锁的threadID。因为偏置锁不会主动释放锁,所以线程1未来再次获取锁时,需要比较当前线程的threadID和Java对象头中的threadID是否一致。如果它们是一致的(或者线程1获取了锁对象),就没有必要使用CAS来锁定和解锁。
如果不是,则需要检查记录在Java对象头中的线程1是否是活动的。如果没有,锁对象被重置为无锁状态,其他线程(线程2)可以竞争将其设置为偏置锁。
如果它是活动的,那么立即查找这个线程(线程1)的堆栈帧信息。如果仍然需要继续持有这个锁对象,那么挂起当前线程1,取消偏置锁,升级到轻量锁。如果线程1不再使用此锁定对象,则将锁定对象状态设置为无锁定状态,并重新偏向新线程。
轻量级锁状态
原因:
轻量级锁考虑了没有太多线程竞争锁对象,线程长时间不持有锁的情况。因为阻塞一个线程需要CPU从用户状态切换到内核状态,成本很高。如果锁在阻塞后很快被释放,成本会比成本多一点,所以在这个时候,简单地不要阻塞线程,让它旋转,等待锁被释放。
升级:
线程1获取轻量级锁时,会先将锁对象的对象头MarkWord复制到线程1的栈帧中创建的用于存储锁记录的空间(称为置换MarkWord),然后用CAS将对象头中的内容替换为线程1存储的置换MarkWord的地址;
如果线程1同时复制对象头(在线程1CAS之前),线程2也准备好获取锁,并将对象头复制到线程2的锁记录空间,但是当线程2CAS发现线程1已经更改了对象头,并且线程2的CAS失败时,那么线程2尝试使用自旋锁等待线程1释放锁。
但是如果旋转时间太长,因为旋转会消耗CPU,所以旋转的次数是有限的,例如10次或100次。如果旋转次数达到线程1还没有释放锁的点,或者线程1还在执行,线程2还在等待旋转,然后另一个线程3来争夺锁对象,那么此时轻量级锁将扩展为重量级锁。重量级锁会阻止除带锁线程之外的所有线程,以防止CPU空闲。
4.对象头布局
例如,对象头:哈希码、对象的年龄、对象锁、锁状态标志、有偏锁(线程)ID、有偏时间、数组长度(数组对象)等。
也就是说,当实例数据:创建一个对象时,对象中的成员变量、方法等等。
对齐填充:对象的大小必须是8字节的整数倍。
标记单词
以32位JVM中存储的内容为例:
锁状态
25 bit
4bit
1bit
2bit
锁标志位
是否是偏向锁
23bit
2bit
气相色谱标记
空的
11
重量锁
指向重量级锁定监视器的指针(取决于互斥操作系统的互斥)
10
轻型锁
指向线程堆栈中锁定记录的指针
00
偏置锁
线程标识
世
对象的生成年龄
一个
01
没有锁
对象的哈希代码
对象的生成年龄
0
01
补充:这里的例子是32位系统,不同位数的系统对象头略有不同
下一篇:没有了