最近翻看leaf源码时,同事提出了一段关于synchronized的idea上报黄是啥情况 ,正好关于synchronized的内容也很久复习过,趁着这个机会讲解一下,在网上翻看了大量文章,发现很
多文章都是有点问题。。本文经过大量实践详细讲解锁升级的一个过程
如有不对,欢迎指正。
本文适合对synchronized有一定了解的同学阅读(至少要知道锁的几个级别)
定义
大家都知道锁分为无锁->偏向锁->轻量级锁->重量级锁
这也是锁的一个升级过程,但是实际上锁的升级并不是按步就班的,可以直接从无锁升级为重量级
很多文章写了锁不可以降级,其实这句话有点误导,锁是不可以降级。但是可以变更为无锁
对象头中的mark word
这个图还是很有用的,很多地方要对着这个图来看
锁的详细升级过程
我们直接从偏向锁讲起,首先配置启动参数-XX:BiasedLockingStartupDelay=0 设置偏向锁启动延时0秒(默认好像是4s)
分为以下情况
单线程没有竞争
线程获取锁了直接就是偏向锁(如果在偏向锁没启动时,则是轻量级锁)
代码:
import lombok.SneakyThrows;
import org.openjdk.jol.info.ClassLayout;
/**
* @author TanJ
* @date 2023/1/6 16:42
*/
public class SyncTest {
@SneakyThrows
public static void main(String[] args) {
Object lock = new Object();
// 打印初始状态
System.out.println(ClassLayout.parseInstance(lock).toPrintable());
synchronized (lock) {
// 打印偏向锁状态
System.out.println(ClassLayout.parseInstance(lock).toPrintable());
// TimeUnit.SECONDS.sleep(2);
}
}
}
结果
这里不管线程是否释放锁,锁状态都是偏向锁(就算已经释放也是)
多线程没有竞争
多线程没有竞争的情况会比较特殊一点,可能会升级为轻量级,也可能还是偏向锁,也有可能会偏向优化
保持偏向锁:多线程没有竞争,前一个线程已经执行完毕
代码:
import lombok.SneakyThrows;
import org.openjdk.jol.info.ClassLayout;
/**
* @author TanJ
* @date 2023/1/6 16:42
*/
public class SyncTest {
static final Object yesLock = new Object();
@SneakyThrows
public static void main(String[] args) {
Runnable runnable = () -> {
synchronized (yesLock) {
System.out.println("线程[" + Thread.currentThread().getName() + ":" + Thread.currentThread().getId() + "]" +
":对象布局:" + System.currentTimeMillis() + ClassLayout.parseInstance(yesLock).toPrintable());
}
};
Thread t0 = new Thread(runnable);
Thread t1 = new Thread(runnable);
t0.start();
t0.join();
System.out.println("t0 线程执行完毕 ");
t1.start();
System.out.println("获取二次并且释放之后,此时的对象布局:" + ClassLayout.parseInstance(yesLock).toPrintable());
}
}
可以看到就算释放了锁。最后都是偏向锁,并且偏向的是第一个线程(并不总是第一个线程,其实当偏向升级或者撤销超过一定阈值,可能会重新指定为别的线程ID)
轻量级锁:没有竞争锁,前一个线程未执行完毕,但是已经释放了锁。
代码:
public static void main(String[] args) throws InterruptedException {
// 打印初始状态
System.out.println(ClassLayout.parseInstance(yesLock).toPrintable());
synchronized (yesLock) {
// 打印偏向锁状态
// System.out.println("hash code"+lock.hashCode());
System.out.println(ClassLayout.parseInstance(yesLock).toPrintable());
TimeUnit.SECONDS.sleep(2);
}
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
synchronized (yesLock){
// 打印轻量级锁,因为上次持有的线程为main线程,正在运行中,但是已经释放了锁
System.out.println(ClassLayout.parseInstance(yesLock).toPrintable());
}
}).start();
TimeUnit.SECONDS.sleep(1);
// 打印无锁
System.out.println(ClassLayout.parseInstance(yesLock).toPrintable());
}
结果:
这边要注意的是,轻量级锁进行释放锁不会变成偏向锁,只会设置为无锁状态,
多线程有竞争
这没啥好说的,直接就是重量级锁
补充
调用锁对象的hase code方法,如果未持有锁,会变成无锁状态(因为hashCode与其它线程ID都在一块存储区域) 如果持有锁,会直接变成重量级锁,因为已经存在锁了,无法释放锁,且hashcode可以存放在Monitor上同理,调用wait方法也会直接升级为重量级锁 这个是因为只有在monitor上才存在waitset
补一张monitor的图吧
2...其它想到了在补充吧,暂时就这些