synchronized 锁与哈希值

synchronized 锁与哈希值

chuxiwen 4,647 2022-09-06

synchronized锁和哈希值的关系

synchronized_hash
synchronized在使用中,从无锁状态,升级为偏向锁(java15以后逐渐废除偏向锁),轻量级锁(自旋锁),重量级锁,在升级为偏向锁后,31位hashCode会被线程指针JavaThread * 所覆盖,这时候如何获取hashCode?

解释哈希和锁状态的关系

synchronized_hash2

两种情况

1、获取hashcode以后上锁

        // java虚拟机默认在启动后5秒后才会开启偏向锁,所以为了方便,此处睡眠6s
        // 也可以jvm调参
        // -XX:BiasedLockingStartupDelay=0   ===> 0s启动偏向锁
        // -XX:+UseBiasedLocking  ===>  启用偏向锁
        TimeUnit.SECONDS.sleep(6);

        Object o = new Object();
        System.out.println("初始对象状态 : \n" + ClassLayout.parseInstance(o).toPrintable());

        System.out.println(o.hashCode());
        System.out.println("获取hashCode后状态 : \n" + ClassLayout.parseInstance(o).toPrintable());

        synchronized (o){
            System.out.println(o.hashCode());
            System.out.println("加锁状态 : " + ClassLayout.parseInstance(o).toPrintable());
        }

测试结果:
synchronized_hash3

根据结果可以得知,初始为偏向锁状态,在获取哈希值以后,偏向锁状态被撤销,锁状态变为无锁状态,因为获取哈希值后对象无法变为偏向锁状态,所以在加锁后,直接变为自旋锁状态(轻量级锁),将哈希值通过线程栈帧的锁记录存储Displaced Mark Word

2、没有获取hashcode后上锁后获取hashcode

        TimeUnit.SECONDS.sleep(6);

        Object o = new Object();
        System.out.println("初始对象状态 : \n" + ClassLayout.parseInstance(o).toPrintable());

        synchronized (o){
            System.out.println(o.hashCode());
            System.out.println("加锁获取hashcode后状态 : \n" + ClassLayout.parseInstance(o).toPrintable());
        }

测试结果:
synchronized_hash4
根据测试结果可见:初始状态为偏向锁状态,并且无hashCode(在获取hashCode之前,在对象头中无hashCode信息)。在为偏向锁状态时获取hashCode,会导致锁状态直接升级为重量级锁状态,并计算出hashCode,ObjectMonitor类里有字段可以记录非加锁状态下的mark word

综上:无锁,轻量级锁,重量级锁都有自己的方式进行hashCode的保存,而偏向锁因为需要保存JavaThread * 指针,和hashCode保存地址冲突,所以偏向锁状态无法和hashCode共存。偏向锁和hashCode出现的先后分别有两种解决方式:1、偏向锁先,偏向锁直接升级为重量级锁 2、获取hashCode先,则跳过偏向锁,直接升级为轻量级锁。


# Java 学习