文章目录
偏向锁
1、偏向锁是什么
大多数情况下,锁不仅不存在多线程竞争,而且总是由同一个线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。JDK1.6之后 偏向锁默认开启
偏向锁是锁状态中最乐观的一种锁:从始至终只有一个线程请求同一把锁
偏向锁对象头的Mark Word:
| 线程ID | Epoch | 对象分代年龄 | 偏向锁的标志 是否偏向锁 0否 1是 | 锁标志位 | |
|---|---|---|---|---|---|
| 线程ID(初始为00…代表未偏向) | Epoch | 对象分代年龄 | 1 | 01 |
2、优缺点
优点
只需要执行一次CAS即可获取锁
采用延迟释放锁策略
锁重入时,只需要判断mark_word.threadId(关于对象头的文章)是否为当前threadId即可
缺点
总体上只针对第一个线程有效,新线程获取锁时,会导致锁膨胀
锁膨胀时,会导致stop the world (STW)
与原生hashcode()互斥,导致偏向锁并非适应于所有的instance
3、偏向锁怎么获取
前提
JVM偏向锁(-XX:+UseBiasedLocking)默认已开启
确认instance可用偏向锁可用,即mark word 偏向锁的标志为 101
当一个线程访问同步块并获取锁时,
会在对象头和栈帧中的锁记录里存储锁偏向的线程ID
,以后该线程在进入和退出同步块时不需要花费CAS操作来加锁和解锁,
而只需简单的测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁,
如果测试成功,
表示线程已经获得了锁,
如果测试失败,
则需要再测试下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁)
,如果没有设置,
则使用CAS竞争锁,竞争成果后为轻量级锁
如果设置了,
则尝试使用CAS将对象头的偏向锁指向当前线程。
引用网络上对于获取偏向锁的总结,解释了批量重偏向和批量撤销偏向的原理
获取偏向锁的步骤
1、验证对象的bias位
如果是0,则该对象不可偏向,应该使用轻量级锁算法。
2、验证对象所属InstanceKlass的prototype的bias位
确认prototype的bias为是否被设置。如果没有设置,则该类所有对象全部不允许被偏向锁定;并且该类所有对象的bias位都需要被重置,使用轻量级锁替换。
3、校验epoch位
校验对象的MarkWord的epoch位是否与该对象所属InstanceKlass的prototype的MarkWord的epoch匹配。如果不匹配,则表明偏向已过期,需要重新偏向。这种情况,偏向线程可以简单地使用原子CAS指令重新偏向于这个锁对象。
4、校验owner线程
比较偏向线程ID与当前线程ID。如果匹配,则表明当前线程已经获得了偏向,可以安全返回。如果不匹配,对象锁被假定为匿名偏向状态,当前线程应该尝试使用CAS指令获得偏向。如果失败的话,就尝试撤销(很可能引入安全点),然后回退到轻量级锁;如果成功,当前线程成功获得偏向,可直接返回。
4、偏向锁的撤销
偏向锁使用了一种等到竞争出现才释放锁的机制,
所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。
偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),
它会首先暂停拥有偏向锁的线程,
然后检查持有偏向锁的线程是否活着,
如果线程不处于活动状态,则将对象头设置成无锁状态,
如果线程仍然活着,恢复到无锁(标记对象不适合作为偏向锁),最后唤醒暂停的线程。这里路神说的是不会有101变为001但感觉这里修改了偏向锁标识然后竞争轻量级锁。因为无法证明得啃jvm源码了,求同存异
需要拿锁的线程再去加锁,只能使用轻量级锁了(批量重偏向除外)
5、可重偏向状态(Rebiasable)
在此状态下,偏向锁的epoch字段是无效的(与锁对象对应的klass的mark_prototype的epoch值不匹配)。下一个试图获取锁对象的线程将会面临这个情况,使用原子CAS指令可将该锁对象绑定于当前线程。在批量重偏向的操作中,未被持有的锁对象都被至于这个状态,以便允许被快速重偏向。本段摘抄自网络,关于重偏向是否CAS稍后讨论
6、BiasedLockingBulkRebiasThreshold 参数是干什么用的
BiasedLockingBulkRebiasThreshold是针对某一Class的实例对象在某一线程偏向锁重偏向的阈值默认是20,
需要注意。如果没达到这个值发生偏向,请check线程id,两个线程是否一样。
当当前线程撤销偏向的次数==20以后线程内该类的实例会处于以下状态 总次数应该在InstanceKlass上,没有翻jvm源码,有待验证
- 已经经过偏向锁撤销,并使用轻量级锁的对象,状态为001 无锁状态demo1
- 当前需要加锁的对象,会check epoch 和偏向锁的标志 重偏向至当前线程 关于重偏向是否CAS逐渐明朗
- 后续未加锁未使用的对象 依然是偏向线程1 demo1
- 已经使用了轻量级锁的对象不可再次使用偏向锁 demo1
- 只要偏向锁在某线程内撤销次数达到此阈值,就修改在class元数据——InstanceKlass中的epoch 是对象和元数据的epoch不一致,而使对象可以重偏向。demo2
- 发生过重偏向的对象不可再次重偏向 自己写demo,这里不提供demo了。需要修改到BiasedLockingBulkRevokeThreshold=41 排除此参数的影响
7、到BiasedLockingBulkRevokeThreshold 参数是干什么用的
BiasedLockingBulkRevokeThreshold 是批量撤销对象的可偏向状态的判断阈值,由上面引用网络上对于获取偏向锁的总结
对后面demo2的现象做出了解释未验证,需要求教子路大仙
- 偏向锁撤销达到BiasedLockingBulkRevokeThreshold 的阈值(默认40)后,修改对象所属InstanceKlass的prototype的bias(偏向锁的标志是否可偏向)位 使该对象以及该类的所有实例,不再使用偏向锁。所以在默认条件下一个类的对象实例,只可以重偏向一次注意和上面的对象区分开。可修改此阈值。调整重偏向次数,或者提前批量撤销偏向
- 偏向锁撤销达到此次数之后,不会影响现有未加锁对象的头(未同步过和已同步过),但这些对象不可使用偏向锁
- 偏向锁撤销达到此次数之后由于类模板InstanceKlass的bias(偏向锁的标志是否可偏向)被设置为0,所以new出来的新对象,是无锁状态,不可使用偏向锁。
8、执行下方demo需要准备的环境
- 一个maven项目
- 引入依赖
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-cli</artifactId>
<version>0.9</version>
</dependency>
- 需要修改jvm项目的运行参数
-XX:+PrintFlagsFinal 打印jvm运行时参数,验证参数修改
-XX:BiasedLockingBulkRebiasThreshold=20 修改批量重偏向阈值
-XX:BiasedLockingBulkRevokeThreshold=40 修改批量撤销偏向阈值。
9、总结和猜想
- 校验epoch位时候发现类模板和对象不一致的时候,可以简单的理解为此对象状态约等于 偏向锁未偏向的状态重新进行偏向加锁
- 当InstanceKlass类模板的偏向锁标识,标识不可偏向的时候。改类所有实例对象均不可进行偏向加锁
- 偏向锁重偏向一次之后不可再次重偏向。
- 已经使用过轻量级锁的对象,不可再次偏向,哪怕线程内触发了批量重偏向
10、参考资料
- 轻量级锁、偏向锁、重量级锁详情
- 是否真的理解了偏向锁、轻量级锁、重量级锁(锁膨胀)、自旋锁、锁消除、锁粗化,知道重偏向吗?
- 偏向锁,轻量级锁,自旋锁,重量级锁的详细介绍
- CAS操作、Java对象头、偏向锁的获取与撤销、轻量级锁的获取与撤销、锁粗化、锁消除
- Java锁事之偏向锁
LAST、 demo代码
//demo1
public class BiasedLockJOLTest {
public static void main(String[] args) throws Exception {
Thread.sleep(6000);
// a = new A();
List<A> list = new ArrayList<>();
List<B> list2 = new ArrayList<>();
for (int i = 0; i < 100; i++) {
list.add(new A());
list2.add(new B());
}
Thread t1 = new Thread() {
String name = "1";
public void run() {
out.printf(name);
for (A a : list) {
synchronized (a) {
if (a == list.get(10))
out.println("t1 预期是偏向锁"+10 + ClassLayout.parseInstance(a).toPrintable());//偏向锁
}
}
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t1.start();
Thread.sleep(5000);
out.println("main 预期是偏向锁 同步结束后不撤销偏向锁,在下次使用的时候进行撤销偏向并膨胀为轻量级锁或者重锁 或重偏向"+10 + ClassLayout.parseInstance(list.get(10)).toPrintable());//偏向锁
Thread t2 = new Thread() {
String name = "2";
public void run() {
out.printf(name);
for(int i = 0;i<100;i++){
A a = list.get(i);
if(i==20){
a= list.get(9);
}
synchronized (a) {
if ( a == list.get(10)) {
out.println("t2 i=10 get(1)预期是无锁" + ClassLayout.parseInstance(list.get(1)).toPrintable());//偏向锁
out.println("t2 i=10 get(10) 预期轻量级锁 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向锁
}
if ( a == list.get(19)) {
out.println("t2 i=19 get(10)预期是无锁" + 10 + ClassLayout.parseInstance(list.get(10)).toPrintable());//无锁 不可偏向
out.println("t2 i=19 get(19) 满足重偏向条件20 预期偏向锁 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向锁
out.println("t2 i=19 get(40) 未同步到的对象,依然偏向t1 " + i + ClassLayout.parseInstance(list.get(40)).toPrintable());//偏向锁
}
if (i == 20) {
out.println("t2 i=20 get(9)预期是轻量级锁,因为无锁 不可偏向的标识不可重新更改为可偏向状态,所以再次锁定之前的对象依然使用了轻量级锁" + 10 + ClassLayout.parseInstance(a).toPrintable());//轻量级锁
}
}
}
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t2.start();
}
}
//demo2
public class BiasedLockJOLTest2 {
static A a;
public static void main(String[] args) throws Exception {
Thread.sleep(6000);
// a = new A();
List<A> list = new ArrayList<>();
List<A> list2 = new ArrayList<>();
List<A> list3 = new ArrayList<>();
for (int i = 0; i < 100; i++) {
list.add(new A());
list2.add(new A());
list3.add(new A());
}
out.println("初始状态" + 10 + ClassLayout.parseClass(A.class).toPrintable());//偏向锁
Thread t1 = new Thread() {
String name = "1";
public void run() {
out.printf(name);
for (A a : list) {
synchronized (a) {
if (a == list.get(10))
out.println("t1 预期是偏向锁" + 10 + ClassLayout.parseInstance(a).toPrintable());//偏向锁
}
}
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t1.start();
Thread.sleep(5000);
out.println("main 预期是偏向锁" + 10 + ClassLayout.parseInstance(list.get(10)).toPrintable());//偏向锁
Thread t2 = new Thread() {
String name = "2";
public void run() {
out.printf(name);
for (int i = 0; i < 100; i++) {
A a = list.get(i);
synchronized (a) {
if (a == list.get(10)) {
out.println("t2 i=10 get(1)预期是无锁" + ClassLayout.parseInstance(list.get(1)).toPrintable());//偏向锁
out.println("t2 i=10 get(10) 预期轻量级锁 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向锁
}
if (a == list.get(19)) {
out.println("t2 i=19 get(10)预期是无锁" + 10 + ClassLayout.parseInstance(list.get(10)).toPrintable());//偏向锁
out.println("t2 i=19 get(19) 满足重偏向条件20 预期偏向锁 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向锁
out.println("A类的对象累计撤销达到20");
}
}
}
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t2.start();
Thread.sleep(5000);
Thread t3 = new Thread() {
String name = "3";
public void run() {
out.printf(name);
for (A a : list2) {
synchronized (a) {
if (a == list2.get(10))
out.println("t3 预期是偏向锁" + 10 + ClassLayout.parseInstance(a).toPrintable());//偏向锁
}
}
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t3.start();
Thread.sleep(5000);
Thread t4 = new Thread() {
String name = "4";
public void run() {
out.printf(name);
for (int i = 0; i < 100; i++) {
A a = list2.get(i);
synchronized (a) {
if (a == list2.get(10)) {
out.println("t4 i=10 get(1)预期是无锁" + ClassLayout.parseInstance(list2.get(1)).toPrintable());//偏向锁
out.println("t4 i=10 get(10) 当前不满足重偏向条件 20 预期轻量级锁 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向锁
}
if (a == list2.get(19)) {
out.println("t4 i=19 get(10)预期是无锁" + 10 + ClassLayout.parseInstance(list2.get(10)).toPrintable());//偏向锁
out.println("t4 i=19 get(19) 当前满足重偏向条件 20 但A类的对象累计撤销达到40 预期轻量级锁 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向锁
out.println("A类的对象累计撤销达到40");
}
if (a == list2.get(20)) {
out.println("t4 i=20 get(20) 当前满足重偏向条件 20 预期轻量级锁 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向锁
}
}
}
}
};
t4.start();
Thread.sleep(5000);
out.println("main 预期是偏向锁" + 10 + ClassLayout.parseInstance(list3.get(0)).toPrintable());//偏向锁
Thread t5 = new Thread() {
String name = "5";
public void run() {
out.printf(name);
for (A a : list3) {
synchronized (a) {
if (a == list3.get(10))
out.println("t5 预期是轻量级锁,A类的对象累计撤销达到40 不可以用偏向锁了" + 10 + ClassLayout.parseInstance(a).toPrintable());//偏向锁
}
}
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t5.start();
Thread.sleep(5000);
out.println("main 预期是偏向锁" + 10 + ClassLayout.parseInstance(list.get(10)).toPrintable());//偏向锁
Thread t6 = new Thread() {
String name = "6";
public void run() {
out.printf(name);
for (int i = 0; i < 100; i++) {
A a = list3.get(i);
synchronized (a) {
if (a == list3.get(10)) {
out.println("t6 i=10 get(1)预期是无锁" + ClassLayout.parseInstance(list3.get(1)).toPrintable());//偏向锁
out.println("t6 i=10 get(10) 预期轻量级锁 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向锁
}
if (a == list3.get(19)) {
out.println("t6 i=19 get(10)预期是无锁" + 10 + ClassLayout.parseInstance(list3.get(10)).toPrintable());//偏向锁
out.println("t6 i=19 get(19) 满足重偏向条件20 但A类的对象累计撤销达到40 不可以用偏向锁了 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向锁
}
}
}
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t6.start();
Thread.sleep(5000);
out.println("由于A撤销锁次数达到默认的 BiasedLockingBulkRevokeThreshold=40 这里实例化的对象 是无锁状态" + ClassLayout.parseInstance(new A()).toPrintable());//偏向锁
out.println("由于B撤销锁次数没达到默认的 BiasedLockingBulkRevokeThreshold=40 这里实例化的对象 是偏向锁可以偏向状态 理论上可以再次验证上面A类的相关操作" + ClassLayout.parseInstance(new B()).toPrintable());//偏向锁
out.println("撤销偏向后状态" + 10 + ClassLayout.parseClass(A.class).toPrintable());//偏向锁
}
}

本文详细介绍了Java中的偏向锁概念,包括它的作用、优缺点、获取与撤销过程,以及可重偏向状态和相关参数的作用。偏向锁适用于单线程访问同步块的场景,以降低获取锁的开销,但在多线程竞争时可能导致锁膨胀和性能下降。文章通过分析锁的状态和操作,帮助读者深入理解Java锁机制。

1331

被折叠的 条评论
为什么被折叠?



