java 锁升级
顺序:无锁 - 偏向锁 - 轻量级锁 - 重量级锁
我们可以通过使用一个 jar 包 jol 观察对象头,查看第一个字节后三位锁的信息。jol 的 maven 坐标:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.8</version>
</dependency>
记住这一个 api 就行了,我只用到了一个:
System.out.println(org.openjdk.jol.info.ClassLayout.parseInstance(new Object()).toPrintable());
必须加个启动参数 -XX:BiasedLockingStartupDelay=0
,不然看不到效果,jdk 1.6 之后默认就是启动偏向锁的,所以不用显式开启偏向锁。但是偏向锁有个 delay,看参数也知道,这个 delay 一般得有五秒,我们是不可能等的,所以把 delay 设置成 0.
直接说结论:
- 一上来是无锁的 第一个字节后三位(后面简称后三位)是 101,
- 如果有一个线程拿到了这个对象的监视器锁,那后三位还是 101,不过后面几个字节会存储和这个线程相关的信息
- 如果又来一个线程,这时有两种情况:前一个线程死了,那虽然新线程获取到锁,还是偏向锁,后三位还是 101,后面线程信息也是不变的;如果前面的那个线程没死,这样就发生了竞争,会导致锁升级,此时又有两种情况:如果第一个线程执行完了但没死,则升级成轻量级锁,后三位 000;如果第一个线程没执行完,发生了实质性的竞争,它会先自旋个十几二十次,还没拿到锁的话直接成为重量级锁,后三位 010。如果这时候又来了一个线程,也就是有三个线程在抢监视器锁,那直接升级为重量级锁。
- 如果在同步块里调用了 wait 方法,那直接升级成重量级锁
- 如果调用过同步对象的 hashCode 方法,那就没法偏向了,因为后面几位保存了对象 hashCode 值,没办法再保存线程栈帧信息了。此时一上来就是轻量级锁。
锁升级后没法降级,也就是升上去就回不来了,只能在有锁和无锁之间切换。比如升到轻量级锁,那没办法再偏向了。
简单贴一下代码,省的有人说我写的没头没尾
Object lock = new Object();
System.out.println("no lock");
System.out.println(ClassLayout.parseInstance(lock).toPrintable());
synchronized (lock) {
System.out.println("biased lock");
System.out.println(ClassLayout.parseInstance(lock).toPrintable());
}
System.out.println("no lock");
System.out.println(ClassLayout.parseInstance(lock).toPrintable());
// 这时候会变成轻量级锁,因为前一个线程 main 没有死
// 如果前面那个同步块在非main线程执行,而且执行到这时那个线程死了,那这里还是偏向锁,不会成为轻量级锁
Thread t = new Thread(() -> {
synchronized (lock) {
System.out.println("t1: light weight lock");
System.out.println(ClassLayout.parseInstance(lock).toPrintable());
}
});
t.start();
t.join();
// 前一个线程已经死了,现在不会成重量级锁
t = new Thread(() -> {
synchronized (lock) {
System.out.println("t2: light weight lock");
System.out.println(ClassLayout.parseInstance(lock).toPrintable());
}
});
t.start();
// 发生了实际竞争,应该成重量级锁了
t = new Thread(() -> {
synchronized (lock) {
System.out.println("t3: heavy weight lock");
System.out.println(ClassLayout.parseInstance(lock).toPrintable());
}
});
t.start();
你们去试吧,绝对和我说的一样,我已经测过了。别忘了加 jvm 参数 -XX:BiasedLockingStartupDelay=0
,很关键