1。锁升级的4种状态:无锁定状态偏向锁定状态轻量级锁状态重量级锁定状态2。总体流程图3。详细介绍偏向锁定状态原因:大部分时候是没有锁竞争的。通常,一个线程会多次获取同一
1。锁升级的4种状态:
无锁定状态
偏向锁定状态
轻量级锁状态
重量级锁定状态
2。总体流程图
3。详细介绍
偏向锁定状态
原因:
大部分时候是没有锁竞争的。通常,一个线程会多次获取同一个锁。所以如果它每次都要争锁的话,会增加很多不必要的成本。为了降低获取锁的成本,引入了偏置锁。
升级:
当线程1访问代码块并获得锁对象时,它将在java对象头和堆栈帧中记录有偏锁的threadID。因为偏置锁不会主动释放锁,所以未来线程1再次获取锁时,需要比较当前线程的threadID和Java对象头中的threadID是否一致。如果一致(或者线程1获取了锁对象),就没有必要使用CAS来加锁和解锁。
如果不是,则需要检查Java对象头中记录的线程1是否是活动的。如果没有,锁对象被重置为解锁状态,其他线程(线程2)可以竞争将其设置为有偏锁。
如果它是活动的,那么立即查找这个线程(线程1)的堆栈帧信息。如果还需要持有这个锁对象,那么挂起当前线程1,取消偏置锁,升级为轻量级锁。如果线程1不再使用这个锁对象,则将锁对象状态设置为解锁状态,并再次将其偏置到新线程。
轻量级锁状态
原因:
轻量级锁考虑了没有很多线程竞争锁对象,线程持有锁的时间很短的情况。因为阻塞一个线程需要CPU从用户态切换到内核态,代价很大。如果阻塞后很快释放锁,代价会比收益多一点,所以这个时候不要阻塞线程,让它旋转,等待锁被释放就行了。
升级:
当线程1获取一个轻量级锁时,会先将锁对象的对象头MarkWord复制到线程1的堆栈框架中创建的空 room(称为DisplaceMarkword)中存储锁记录,然后用CAS将对象头中的内容替换为线程1存储的锁记录(DisplaceMarkword)的地址;
如果线程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
GC标记空11重量级锁指向重量级锁Monitor的指针(依赖Mutex操作系统的互斥)10轻量级锁指向线程栈中锁记录的指针pointer to Lock Record00偏向锁线程IDEpoch对象分代年龄101无锁对象的hashCode对象分代年龄001
补充:这里的例子是32位系统,不同位的系统对象头略有不同