轻量级锁:
使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以 使用轻量级锁来优化。
轻量级锁原理
1.创建锁记录(Lock Record)对象,每个线程都的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的 Mark Word
2.让锁记录中 Object reference 指向锁对象,并尝试用 cas 替换 Object 的 Mark Word,将 Mark Word 的值存入锁记录
- 如果 cas 替换成功,对象头中存储了 锁记录地址和状态 00 ,表示由该线程给对象加锁,这时图示如下
4.如果 cas 失败,有两种情况
a.如果是其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,进入锁膨胀过程
b.如果是自己执行了 synchronized 锁重入,那么再添加一条 Lock Record 作为重入的计数
当退出 synchronized 代码块(解锁时)如果有取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重 入计数减一
当退出 synchronized 代码块(解锁时)锁记录的值不为 null,这时使用 cas 将 Mark Word 的值恢复给对象头
a.成功,则解锁成功
b.失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程
锁膨胀
锁膨胀原理
如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有 竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。
java
static Object obj = new Object();
public static void method1() {
synchronized( obj ) {
// 同步块
}
}
当 Thread-1 进行轻量级加锁时,Thread-0 已经对该对象加了轻量级锁
这时 Thread-1 加轻量级锁失败,进入锁膨胀流程
即为Object 对象申请 Monitor 锁,让 Object 指向重量级锁地址
然后自己进入 Monitor 的 EntryList BLOCKED
当 Thread-0 退出同步块解锁时,使用 cas 将 Mark Word 的值恢复给对象头,失败。这时会进入重量级解锁 流程,即按照 Monitor 地址找到 Monitor 对象,设置 Owner 为 null,唤醒 EntryList 中 BLOCKED 线程
重量级锁:每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针
偏向锁
轻量锁缺陷:
轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。
轻量锁优化
java
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。以后只要不发生竞争,这个对象就归该线程所有。如下图所示。(偏向第一个获得锁的线程,并把这个线程存储在对象头里,之后只要是该线程来获取锁,都能快速批准)
总结
轻量级锁
栈帧中包含锁记录(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是否是自己
摘自哔哩哔哩黑马程序员笔记