Java锁synchronized关键字学习系列之偏向锁升级
Posted c.
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java锁synchronized关键字学习系列之偏向锁升级相关的知识,希望对你有一定的参考价值。
Java锁synchronized关键字学习系列之偏向锁升级
前面几篇博文已经简单介绍了偏向锁了。《Java锁synchronized关键字学习系列之偏向锁》 还有《Java锁synchronized关键字学习系列之批量重偏向和批量撤销》
这篇博文我们来更进一步的来讲一讲偏向锁的升级。
无锁
之前我们提到过偏向锁开启的延迟时间,在这个偏向延迟内对象处于为无锁态。如果关闭偏向锁启动延迟、或是经过偏向锁开启的延迟时间之后且没有线程竞争对象的锁,那么对象会进入无锁可偏向状态。
准确来说,无锁可偏向状态应该叫做匿名偏向(Anonymously biased)状态,因为这时对象的mark word中后三位已经是101,但是threadId指针部分仍然全部为0,它还没有向任何线程偏向。综上所述,对象在刚被创建时,根据jvm的配置对象可能会处于无锁或匿名偏向 两个状态。
并且在无锁不可偏向状态下,如果有线程试图获取锁,那么将跳过升级偏向锁的过程,直接升级为轻量级锁,这一点在我之前的博客也有讲到,不明白的同学可以回去看一看。
额外补充的一点就是匿名偏向状态下,如果调用系统的hashCode()
方法,会使对象回到无锁态,并在markword中写入hashCode。并且在这个状态下,如果有线程尝试获取锁,会直接从无锁升级到轻量级锁,不会再升级为偏向锁。这个我们可以用代码来验证一下:
public static void main(String[] args) {
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable()); //偏向锁
System.out.println(Integer.toHexString(o.hashCode()));
System.out.println(ClassLayout.parseInstance(o).toPrintable());//无锁
synchronized (o){
System.out.println(ClassLayout.parseInstance(o).toPrintable());//轻量级锁
}
}
运行结果:
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 10 00 00 (00000000 00010000 00000000 00000000) (4096)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
491cc5c9
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 c9 c5 1c (00000001 11001001 11000101 00011100) (482724097)
4 4 (object header) 49 00 00 00 (01001001 00000000 00000000 00000000) (73)
8 4 (object header) 00 10 00 00 (00000000 00010000 00000000 00000000) (4096)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 50 f0 8f 2d (01010000 11110000 10001111 00101101) (764407888)
4 4 (object header) fa 00 00 00 (11111010 00000000 00000000 00000000) (250)
8 4 (object header) 00 10 00 00 (00000000 00010000 00000000 00000000) (4096)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Process finished with exit code 0
从运行结果我们也可以看出来,一开始是偏向锁(更准确的说是匿名偏向), 经过hashCode之后变成了无锁,并且markword中写入hashCode,之后我们进行synchronized
加锁操作之后可以看到,变成了轻量级锁了。
偏向锁原理
匿名偏向状态是偏向锁的初始状态,在这个状态下第一个试图获取该对象的锁的线程,会使用CAS操作(汇编命令CMPXCHG)尝试将自己的threadID写入对象头的mark word中,使匿名偏向状态升级为已偏向(Biased)的偏向锁状态。在已偏向状态下,线程指针threadID非空,且偏向锁的时间戳epoch为有效值。
如果之后有线程再次尝试获取锁时,需要检查mark word中存储的threadID是否与自己相同即可,如果相同那么表示当前线程已经获得了对象的锁,不需要再使用CAS操作来进行加锁。
如果mark word中存储的threadID与当前线程不同,那么将执行CAS操作,试图将当前线程的ID替换mark word中的threadID。只有当对象处于下面两种状态中时,才可以执行成功:
- 对象处于匿名偏向状态
- 对象处于可重偏向(Rebiasable)状态,新线程可使用CAS将threadID指向自己
如果对象不处于上面两个状态,说明锁存在线程竞争,在CAS替换失败后会执行偏向锁撤销操作。偏向锁的撤销需要等待全局安全点Safe Point(安全点是 jvm为了保证在垃圾回收的过程中引用关系不会发生变化设置的安全状态,在这个状态上会暂停所有线程工作),在这个安全点会挂起获得偏向锁的线程。
在暂停线程后,会通过遍历当前jvm的所有线程的方式,检查持有偏向锁的线程状态是否存活:
如果线程还存活,且线程正在执行同步代码块中的代码,则升级为轻量级锁
如果持有偏向锁的线程未存活,或者持有偏向锁的线程未在执行同步代码块中的代码,则进行校验是否允许重偏向:
不允许重偏向,则撤销偏向锁,将mark word升级为轻量级锁,进行CAS竞争锁
允许重偏向,设置为匿名偏向锁状态,CAS将偏向锁重新指向新线程
完成上面的操作后,唤醒暂停的线程,从安全点继续执行代码。
参考:再谈synchronized锁升级
可以使用流程图总结上面的过程:
批量重偏向和批量撤销
这里谈到的批量重偏向和批量撤销可以看看我之前的博文《Java锁synchronized关键字学习系列之批量重偏向和批量撤销》或者参考一下再谈synchronized锁升级
偏向锁升级
在上面的过程中,我们已经知道了匿名偏向状态可以变为无锁态或升级为偏向锁,接下来看一下偏向锁的其他状态的改变
偏向锁升级轻量级锁
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
synchronized (o) {
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
Thread thread = new Thread(() -> {
synchronized (o) {
System.out.println("--THREAD--:" + ClassLayout.parseInstance(o).toPrintable());
}
});
thread.start();
thread.join();
System.out.println("--END--:" + ClassLayout.parseInstance(o).toPrintable());
}
结果:
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 c0 f0 42 (00000101 11000000 11110000 01000010) (1123074053)
4 4 (object header) 14 02 00 00 (00010100 00000010 00000000 00000000) (532)
8 4 (object header) 00 10 00 00 (00000000 00010000 00000000 00000000) (4096)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
--THREAD--:java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) d8 f4 5f 49 (11011000 11110100 01011111 01001001) (1231025368)
4 4 (object header) b3 00 00 00 (10110011 00000000 00000000 00000000) (179)
8 4 (object header) 00 10 00 00 (00000000 00010000 00000000 00000000) (4096)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
--END--:java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 10 00 00 (00000000 00010000 00000000 00000000) (4096)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
从结果可以看到偏向锁升级为轻量级锁,在执行完成同步代码后释放锁,变为无锁不可偏向状态。
偏向锁升级重量级锁
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
Thread thread = new Thread(() -> {
synchronized (o) {
System.out.println("--THREAD1--:" + ClassLayout.parseInstance(o).toPrintable());
try {
o.wait(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("--THREAD END--:" + ClassLayout.parseInstance(o).toPrintable());
}
});
thread.start();
thread.join();
TimeUnit.SECONDS.sleep(3);
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
结果:
--THREAD1--:java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 20 ea 5e (00000101 00100000 11101010 01011110) (1592401925)
4 4 (object header) f4 01 00 00 (11110100 00000001 00000000 00000000) (500)
8 4 (object header) 00 10 00 00 (00000000 00010000 00000000 00000000) (4096)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
--THREAD END--:java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 82 2f a1 5e (10000010 00101111 10100001 01011110) (1587621762)
4 4 (object header) f4 01 00 00 (11110100 00000001 00000000 00000000) (500)
8 4 (object header) 00 10 00 00 (00000000 00010000 00000000 00000000) (4096)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 00 10 00 00 (00000000 00010000 00000000 00000000) (4096)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Process finished with exit code 0
从结果可以看出一开始是偏向锁,经过wait()
方法之后,就变成重量级锁。同步代码块之后锁释放,变成了无锁。
这里是因为wait()方法调用过程中依赖于重量级锁中与对象关联的monitor,在调用wait()方法后monitor会把线程变为WAITING状态,所以才会强制升级为重量级锁。除此之外,调用hashCode方法时也会使偏向锁直接升级为重量级锁。
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
Thread thread = new Thread(() -> {
synchronized (o) {
System.out.println("--THREAD1--:" + ClassLayout.parseInstance(o).toPrintable());
System.out.println(Integer.toHexString(o.hashCode()));
System.out.println("--THREAD END--:" + ClassLayout.parseInstance(o).toPrintable());
}
});
thread.start();
thread.join();
TimeUnit.SECONDS.sleep(3);
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
结果:
--THREAD1--:java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 40 06 64 (00000101 01000000 00000110 01100100) (1678131205)
4 4 (object header) b4 01 00 00 (10110100 00000001 00000000 00000000) (436)
8 4 (object header) 00 10 00 00 (00000000 00010000 00000000 00000000) (4096)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
4115a3d8
--THREAD END--:java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 82 aa d7 63 (10000010 10101010 11010111 01100011) (1675078274)
4 4 (object header) b4 01 00 00 (10110100 00000001 00000000 00000000) (436)
8 4 (object header) 00 10 00 00 (00000000 00010000 00000000 00000000) (4096)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 d8 a3 15 (00000001 11011000 10100011 00010101) (363059201)
4 4 (object header) 41 00 00 00 (01000001 00000000 00000000 00000000) (65)
8 4 (object header) 00 10 00 00 (00000000 00010000 00000000 00000000) (4096)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
所以具体的流程图如下:
参考
源代码
以上是关于Java锁synchronized关键字学习系列之偏向锁升级的主要内容,如果未能解决你的问题,请参考以下文章
Java锁synchronized关键字学习系列之批量重偏向和批量撤销
Java锁synchronized关键字学习系列之轻量级锁升级