synchronized锁膨胀过程

轻量级锁:

使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以 使用轻量级锁来优化。

轻量级锁原理

1.创建锁记录(Lock Record)对象,每个线程都的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的 Mark Word

synchronized锁膨胀过程_第1张图片

2.让锁记录中 Object reference 指向锁对象,并尝试用 cas 替换 Object 的 Mark Word,将 Mark Word 的值存入锁记录

synchronized锁膨胀过程_第2张图片

3. 如果 cas 替换成功,对象头中存储了 锁记录地址和状态 00 ,表示由该线程给对象加锁,这时图示如下

synchronized锁膨胀过程_第3张图片

4.如果 cas 失败,有两种情况

a.如果是其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,进入锁膨胀过程

b.如果是自己执行了 synchronized 锁重入,那么再添加一条 Lock Record 作为重入的计数

synchronized锁膨胀过程_第4张图片

当退出 synchronized 代码块(解锁时)如果有取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重 入计数减一

synchronized锁膨胀过程_第5张图片

当退出 synchronized 代码块(解锁时)锁记录的值不为 null,这时使用 cas 将 Mark Word 的值恢复给对象头

a.成功,则解锁成功

b.失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程

锁膨胀

锁膨胀原理

如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有 竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。

static Object obj = new Object();
public static void method1() {
 synchronized( obj ) {
 // 同步块
 }
}

当 Thread-1 进行轻量级加锁时,Thread-0 已经对该对象加了轻量级锁

synchronized锁膨胀过程_第6张图片

这时 Thread-1 加轻量级锁失败,进入锁膨胀流程

即为Object 对象申请 Monitor 锁,让 Object 指向重量级锁地址

然后自己进入 Monitor 的 EntryList BLOCKED

synchronized锁膨胀过程_第7张图片

当 Thread-0 退出同步块解锁时,使用 cas 将 Mark Word 的值恢复给对象头,失败。这时会进入重量级解锁 流程,即按照 Monitor 地址找到 Monitor 对象,设置 Owner 为 null,唤醒 EntryList 中 BLOCKED 线程

重量级锁:每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针

偏向锁

轻量锁缺陷:

轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。

轻量锁优化

static final Object obj = new Object();
public static void m1() {
 synchronized( obj ) {
 // 同步块 A
 m2();
 }
}
public static void m2() {
 synchronized( obj ) {
 // 同步块 B
 m3();
 }
}
public static void m3() {
 synchronized( obj ) {
 // 同步块 C
 }
}

Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现 这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有。如下图所示。(偏向第一个获得锁的线程,并把这个线程存储在对象头里,之后只要是该线程来获取锁,都能快速批准)

synchronized锁膨胀过程_第8张图片

synchronized锁膨胀过程_第9张图片

总结

轻量级锁

栈帧中包含锁记录(Lock Record:lock record 地址;Object reference)

先创建锁记录,Object reference指向锁对象(obect),并通过cas替换lock record地址和objec中的Mark Word(锁记录地址和状态);

如果失败,可能是其他线程持有了该object轻量级锁,则进入锁膨胀;也可能是自己重入,则再添加一条锁记录用来计数,同时该锁记录object reference指向object,lock record为null.

当退出synchronized时,如果发现有null的记录,表明有重入,然后重置锁记录;若不为null,则使用cas还原(Mark word恢复给Object,lock record地址恢复给lock record);如果失败,则表明进入了锁膨胀环节,则进入重量锁解锁过程。

锁膨胀

当Thread1尝试进行轻量级锁,无法cas成功,说明有其他线程(Thread0)给该Object对象上锁,此时轻量级锁升级成重量级锁;为Object对象申请Monitor锁,Object指向该Monitor,然后Thread1会进入Monitor的EntryList中阻塞,而Monitor中的Owner会指向Thread0中lock recotd中Object reference。当Thread0退出Synchronized代码块时,发现使用cas无法解锁成功,Thread0会依据Monitor地址找到Monitor对象,设置Owner为null,然后唤醒EntryList中的Thread1

偏向锁

只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS,因此只需要检查该线程ID是否是自己

 摘自哔哩哔哩黑马程序员笔记

你可能感兴趣的:(java,jvm,开发语言)