目 录CONTENT

文章目录

synchronized锁升级之无锁和偏向锁

筱晶哥哥
2023-05-01 / 0 评论 / 0 点赞 / 29 阅读 / 5721 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2024-03-23,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

其实在 JDK 1.6之前,synchronized 还是一个重量级锁,是一个效率比较低下的锁,但是在JDK 1.6后,Jvm为了提高锁的获取与释放效率对(synchronized )进行了优化,引入了 偏向锁 和 轻量级锁 ,从此以后锁的状态就有了四种(无锁、偏向锁、轻量级锁、重量级锁),并且四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级。

本文先介绍synchronized锁升级之无锁和偏向锁,后续详细介绍下synchronized锁升级过程。

无锁

为了优化synchronized锁的效率,在JDK6中,HotSpot虚拟机开发团队提出了锁升级的概念,包括偏向锁、轻量级锁、重量级锁等,锁升级指的就是“无锁 --> 偏向锁 --> 轻量级锁 --> 重量级锁”。

synchronized同步锁相关信息保存到锁对象的对象头里面的Mark Word中,锁升级功能主要是依赖Mark Word中锁标志位和是否偏向锁标志位来实现的。

从上图我们可以看到,无锁对应的锁标志位是“01”,是否偏向锁标志是“0”。

public class NoLock {
    public static void main(String[] args) {
        Object objLock = new Object();
        // 需要注意,只有调用了hashCode(),对象头中的MarkWord才会保存对应的hashCode值,否则全部是0
        System.out.println("10进制: " + objLock.hashCode());
        System.out.println("2进制: " + Integer.toBinaryString(objLock.hashCode()));
        System.out.println("16进制: " + Integer.toHexString(objLock.hashCode()));
        System.out.println(ClassLayout.parseInstance(objLock).toPrintable());
    }
}

MarkWord对象头总共占8个字节,共64位,我们按照上图中“1 -> 8”,也就是从后面往前面拼接起来:

00000000 00000000 00000000 01110100 10100001 01000100 10000010 00000001

红色字体:25位,不使用;

蓝色字体:31位,表示对象的hashCode,可以看到,跟程序输出结果对应的上;

● 橙色字体:1位,不使用;

● 青色字体:4位,对象的分代年龄;

紫色字体:1位,表示对象是否启用偏向锁标记。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁;本示例中,是无锁,所以是0;

● 黄色字体:2位,表示锁状态的标记位;本示例中,是无锁,所以是01;

偏向锁

什么是偏向锁?

HotSpot作者经过研究实践发现,在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低,引进了偏向锁。

偏向锁的“偏”,它的意思是锁会偏向于第一个获得它的线程,会在对象头(Mark Word中)记录锁偏向的线程ID,以后该线程进入和退出同步块时只需要检查是否为偏向锁、锁标志位以及ThreadID即可。

如下图是偏向锁对象头MarkWord布局:

public static void main(String[] args) {
    Object objLock = new Object();
    new Thread(() -> {
        synchronized (objLock) {
            System.out.println(ClassLayout.parseInstance(objLock).toPrintable());
        }
    }, "t1").start();
}

如上我们看到,markword的倒数三位是000,根据前面的图,000表示的是轻量级锁,此时只有一个线程访问,为什么输出来的不是偏向锁标识101呢?

原因其实是偏向锁在Java 6之后是默认启用的,但在应用程序启动几秒钟(默认延迟4秒)之后才会激活,可以使用 -XX:BiasedLockingStartupDelay=0 参数关闭延迟,让其在程序启动时立刻启动。当然为了演示,也可以在程序中休眠5秒,等待偏向锁激活后。

下面我们添加运行时JVM参数,再次启动程序,观察内存布局:

可以看到关闭偏向锁延迟后,当前锁就是偏向锁了。

偏向锁原理

在偏向锁第一次被线程拥有的时候,在偏向锁的MarkWord中,有一块区域用来记录偏向线程的ID。

注意,偏向锁只有遇到其它线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动释放偏向锁的。

偏向锁的撤销

前面提到,大部分情况下,锁都是被同一个线程获取到,持有偏向锁的线程不会主动释放锁。

那么大部分情况下,不会涉及到偏向锁的撤销,当有另外的线程尝试竞争偏向锁的时候,这个时候才会涉及偏向锁的撤销流程。

0

评论区