大多数情况下,锁不仅不存在多线程竞争,而且总是由同一个线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。JDK1.6之后 偏向锁默认开启
偏向锁是锁状态中最乐观的一种锁:从始至终只有一个线程请求同一把锁
偏向锁对象头的Mark Word:
线程ID | Epoch | 对象分代年龄 | 偏向锁的标志 是否偏向锁 0否 1是 | 锁标志位 | |
---|---|---|---|---|---|
线程ID(初始为00…代表未偏向) | Epoch | 对象分代年龄 | 1 | 01 |
只需要执行一次CAS即可获取锁
采用延迟释放锁策略
锁重入时,只需要判断mark_word.threadId(关于对象头的文章)是否为当前threadId即可
总体上只针对第一个线程有效,新线程获取锁时,会导致锁膨胀
锁膨胀时,会导致stop the world (STW)
与原生hashcode()互斥,导致偏向锁并非适应于所有的instance
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指令获得偏向。如果失败的话,就尝试撤销(很可能引入安全点),然后回退到轻量级锁;如果成功,当前线程成功获得偏向,可直接返回。
偏向锁使用了一种等到竞争出现才释放锁的机制,
所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。
偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),
它会首先暂停拥有偏向锁的线程,
然后检查持有偏向锁的线程是否活着,
如果线程不处于活动状态,则将对象头设置成无锁状态,
如果线程仍然活着,恢复到无锁(标记对象不适合作为偏向锁),最后唤醒暂停的线程。这里路神说的是不会有101变为001但感觉这里修改了偏向锁标识然后竞争轻量级锁。因为无法证明得啃jvm源码了,求同存异
需要拿锁的线程再去加锁,只能使用轻量级锁了(批量重偏向除外)
在此状态下,偏向锁的epoch字段是无效的(与锁对象对应的klass的mark_prototype的epoch值不匹配)。下一个试图获取锁对象的线程将会面临这个情况,使用原子CAS指令可将该锁对象绑定于当前线程。在批量重偏向的操作中,未被持有的锁对象都被至于这个状态,以便允许被快速重偏向。本段摘抄自网络,关于重偏向是否CAS稍后讨论
BiasedLockingBulkRebiasThreshold是针对某一Class的实例对象在某一线程偏向锁重偏向的阈值默认是20,
需要注意。如果没达到这个值发生偏向,请check线程id,两个线程是否一样。
当当前线程撤销偏向的次数==20以后线程内该类的实例会处于以下状态 总次数应该在InstanceKlass上,没有翻jvm源码,有待验证
BiasedLockingBulkRevokeThreshold 是批量撤销对象的可偏向状态的判断阈值,由上面引用网络上对于获取偏向锁的总结
对后面demo2的现象做出了解释未验证,需要求教子路大仙
<dependency>
<groupId>org.openjdk.jolgroupId>
<artifactId>jol-cliartifactId>
<version>0.9version>
dependency>
-XX:+PrintFlagsFinal 打印jvm运行时参数,验证参数修改
-XX:BiasedLockingBulkRebiasThreshold=20 修改批量重偏向阈值
-XX:BiasedLockingBulkRevokeThreshold=40 修改批量撤销偏向阈值。
//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());//偏向锁
}
}