目录
认识锁升级
Java锁是为了防止进程多线程并发访问临界资源造成数据安全问题的
申请锁需要发起系统调用,向操作系统内核申请锁,是一个比较重的操作
Jvm对锁做了多次优化,避免上来就直接怼成重量锁。
于是乎就有锁升级,带着学习的目的一探究竟,而不是浮在表面。
锁升级的过程就是无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁随着多线程竞争激烈锁升级的过程
synchronized (object)
是对象上锁,竞争锁的是线程
哪个线程抢到锁了,需要在对象上有一个标记,轻量级锁对象对象头上需要存储线程的线程栈中的锁记录(lock record)
重量级锁需要向操作系统申请了,重量级锁和对象的关联是这样的,
对象头存储的是指向一个monitor的结构,线程栈中又有指针指向对象,monitor中会指向哪个线程获得了锁
monitor结构
monitor是重量级锁,需要向操作系统内核申请锁,比较重,也比较耗资源
在这里提前贴个图做简单介绍
轻量级锁
两个方法同步块,利用同一个对象加锁
创建锁记录(Lock Record)对象,每个线程都的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的 Mark Word
让锁记录中 Object reference 指向锁对象,并尝试用 cas 替换 Object 的 Mark Word,将 Mark Word 的值存 入锁记录
如果 cas 替换成功,对象头中存储了 锁记录地址和状态 00 ,表示由该线程给对象加锁
如果有锁重入的话,还会生成一个lock record, 重入计数加一
当有退出锁同步代码块时,再会干掉一个lock record,重入计数减一
当都解锁成功时,会cas恢复对象头的mark word
轻量级锁也称自旋锁,线程等待时会占用CPU资源自旋等待
重量级锁
偏向锁
前面的轻量级锁和重量级锁都相对好理解,偏向锁相对晦涩些,一起来看看。。。
偏向锁 – markword 上记录当前线程指针,下次同一个线程加锁的时候,不需要争用,只需要判断线程指针是否同一个,所以,偏向锁,偏向加锁的第一个线程 。
默认情况偏向锁有个4秒的时延,
所以会延迟开启偏向锁。
如果没有等到偏向锁开启就上锁了,就直接升级到轻量级锁。
new出来的对象,如果等到偏向锁开启就是匿名对象,markword已经表明上了偏向锁,
但是线程id还是0,就是目前还没有偏向任何人,谁先来我偏向谁,偏向之后,如果再也竞争就会撤销偏向锁升级为轻量级锁。
如果有线程上偏向锁,markword线程id就是上偏向锁成功的线程id
package pers.wmx.springbootfreemarkerdemo.jol;
import java.util.concurrent.TimeUnit;
import org.openjdk.jol.info.ClassLayout;
/**
* @author wangmingxin03
* Created on 2021-11-19
*/
public class Main2 {
public static void main(String[] args) throws InterruptedException {
// 偏向锁是过几秒钟才启动,Jvm启动会有大量线程开启偏向锁延迟些启用
// 所以可能大家试过好多次,都是无锁直接到轻量级锁或者重量级锁。。。
// 尝试睡几秒,等一波偏向锁升级
Thread.sleep(TimeUnit.SECONDS.toMillis(5));
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable()); // 101 (偏向锁)
// 只要主线程上偏向锁
synchronized(o) {
System.out.println(ClassLayout.parseInstance(o).toPrintable()); // 101 (偏向锁)
}
Thread t1 = new Thread(() -> {
synchronized (o) {
System.out.println("t1 lock " + Thread.currentThread().getName());
System.out.println(ClassLayout.parseInstance(o).toPrintable()); // 000 (偏向锁撤销, 升级轻量级锁)
}
});
t1.start();
Thread t2 = new Thread(() -> {
synchronized (o) {
System.out.println("t2 lock " + Thread.currentThread().getName());
System.out.println(ClassLayout.parseInstance(o).toPrintable()); // 010 (重量级锁)
}
});
t2.start();
}
}
如果没有前面sleep几秒的话
上来对象是无锁然后升级为轻量级锁,上面我们也解释了为什么有偏向锁启动时延
有JVM启动参数可以调整偏向锁启动时延,也可以开启合禁用偏向锁
//关闭延迟开启偏向锁
-XX:BiasedLockingStartupDelay=0
//禁止偏向锁
-XX:-UseBiasedLocking
//启用偏向锁
-XX:+UseBiasedLocking
感受一波
现在把时延调成0,默认已启动对象就上偏向锁,我们实验下
package pers.wmx.springbootfreemarkerdemo.jol;
import org.openjdk.jol.info.ClassLayout;
/**
* @author wangmingxin03
* Created on 2021-11-19
*/
public class Main3 {
public static void main(String[] args) {
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable()); // 101 (偏向锁)
// 只要主线程上偏向锁
synchronized(o) {
System.out.println(ClassLayout.parseInstance(o).toPrintable()); // 101 (偏向锁)
}
}
}
锁升级过程
总结来说就是 new -> 偏向锁 -> 轻量级锁 -> 重量级锁
对应对象头 markword 如下图
说点什么
您将是第一位评论人!