java锁升级相关的机制

以下都是基于jdk8的版本,并且需要了解对象中Mark Word、Klass Ponint等结构,这里只做验证,内存依赖用的是0.9版本的,二进制是倒着输出的,所以图中红色框和绿色框仅仅代表位置,不是很准确,大家知道这一点就好了

  1. java是默认开启偏向锁,但是是延迟开启,即在系统启动的一段时间内是没有偏向锁的,几秒后,偏向锁开启。以下命令可以调整相关参数:
    //关闭延迟开启偏向锁
    -XX:BiasedLockingStartupDelay=0
    //禁止偏向锁
    -XX:-UseBiasedLocking 
    //启用偏向锁
    -XX:+UseBiasedLocking 
    
  • 验证默认情况下锁机制,先引入对象内存分析依赖包
    <dependency>
      <groupId>org.openjdk.jol</groupId>
      <artifactId>jol-core</artifactId>
      <version>0.9</version>
    </dependency>
    
  • 测试代码,ClassLayout.parseInstance(new Object()).toPrintable():打印对象内存相关信息
    public static void main (String[] args) throws Exception {
    	//这里需要打印不同的对象,因为打印同一个对象的话,已经加载了,状态前后就一致了,观察不到效果
      System.out.println("启动时:" + ClassLayout.parseInstance(new Demo()).toPrintable());
      Thread.sleep(4000);
      System.out.println("启动后:" + ClassLayout.parseInstance(new Demo()).toPrintable());
    }
    
    请添加图片描述
    以上只验证默认情况,其他情况可以在idea中增加vm的启动参数进行验证
  1. 现在关闭偏向锁延迟,验证锁升级的几种情况,(一定要关闭延迟,否则验证结果会不同)
  • 先申明一点,开启了偏向锁,但是没有synchronized代码块时,对象的Mark Word中会存储线程id、偏向锁的标志位,锁的标志位信息,没有竞争的情况下,线程id全是0,代表没有线程竞争过,当有线程开始竞争时,线程id将改为当前线程。测试代码如下:

    public static void main (String[] args) throws Exception {
        Demo obj = new Demo();	//加锁的对象
        System.out.println("加锁前:" + ClassLayout.parseInstance(obj).toPrintable());
        synchronized (obj) {
            System.out.println("加锁中:" + ClassLayout.parseInstance(obj).toPrintable());
        }
        System.out.println("解锁后:" + ClassLayout.parseInstance(obj).toPrintable());
    }
    

    请添加图片描述
    结论如果只有一个线程t1竞争锁,此时为偏向锁,只会更改对象mark word中线程id,释放锁后,线程id不会清空,当下次t1再次竞争,发现为t1的线程id,此时直接进入,不会通过cas比较,这样节约资源。

  • 第二种情况,两个线程t1和t2先后持有锁,但是不会竞争,一定是t1释放锁后,t2再尝试获取锁:

    public static void main (String[] args) throws Exception {
        Demo obj = new Demo();
        System.out.println("t1加锁前:" + ClassLayout.parseInstance(obj).toPrintable());
        new Thread(() -> {
            synchronized (obj) {
                //这里就不打印了,主要观察t2线程的状态
            }
            System.out.println("===============================> t1已经释放锁了");
        }, "t1").start();
    
        new Thread(() -> {
            try {
                Thread.sleep(5000); //这里休眠5秒,一定要等t1释放锁后再去获取锁,不能产生竞争锁的情况
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t2加锁前:" + ClassLayout.parseInstance(obj).toPrintable());
            synchronized (obj) {
                System.out.println("t2加锁中:" + ClassLayout.parseInstance(obj).toPrintable());
            }
            System.out.println("t2解锁后:" + ClassLayout.parseInstance(obj).toPrintable());
        }, "t2").start();
    
    }
    

    请添加图片描述
    结论t1线程先持有锁,加锁前和加锁中和第一点相同,但是当t1释放锁后,t2获取锁时,此时发现线程id已经是t1的,但是不知道偏向锁是否被释放,所以此时必须升级为轻量级锁,锁的后两位改为00,表示轻量级锁,前面位数代表lock record的地址,当t2释放锁后,对象mark word恢复最原始状态。

  • 第三种,两个线程不是先后了,是同时竞争锁,这里就不贴图了,直接说下结论:
    结论刚开始对象中仍然是偏向锁,t1持有锁时,改为t1的id,此时t1还没有释放锁,t2开始竞争锁,此时锁膨胀为重量级锁,Mark Word中状态变为10,地址为Monitor的地址,t2进入阻塞队列中等待,t1释放锁,t2获取锁并且释放后,Mark Word恢复成无锁状态,和第二点相同。

  • 第四种讲一下批量重定向,和第二种情况相同,对象从偏向锁->轻量级锁->无锁,这样同一个对象先后被两个线程持有锁,此时锁的状态会发生上述的变化,这种情况比较消耗资源,假如现在有19个对象都是发生上述情况,那么从20个对象开始,加锁前还是获取的t1线程id,加锁中就把线程id直接改为t2的线程id,释放锁后也不会改变,剩下的对象都是同样的步骤,此时就相当于批量替换为t2的线程id。

  • 第五种:批量撤销,第四种讲了同一个对象被两个线程先后持有锁,会发生偏向锁撤销的情况,如果有多个对象发生这种情况,当阈值达到20的时候剩下的对象会在持有锁后改为t2线程的id。但是这时如果有第三个线程再持有锁,此时前面19个对象变化为无锁->轻量级锁->无锁,但是从第20个对象开始,在持有锁前Mark word为t2的id,状态为偏向锁,这时候持有锁后会继续发生偏向锁撤销的情况,在第39个对象偏向锁撤销后,这个类创建的对象仍然是偏向锁,从第40个对象开始,因为偏向锁撤销次数太多,这个类创建的对象不再是偏向锁状态了,而直接是无锁状态。下面张贴代码:

    //为了使结果更直观,我把返回的对象内存信息的Mark Word的二进制提取出来,
    //顺序做了颠倒,可以从左往右看
    public static String parse(String str) {
        String s = str.split("\r\n")[2].substring(76, 111);
        String[] arr = s.split(" ");
        String res = "";
        for (int i = arr.length - 1; i >= 0; i--) {
            res += arr[i] + " ";
        }
        return res;
    }
    
    public static void main (String[] args) throws Exception {
    
        List<TwoDemo> list = new ArrayList<>();
    
        int num =38;	//对象的个数
        new Thread(() -> {
            for (int i = 0; i < num; i++) {
                TwoDemo obj = new TwoDemo();
                System.out.println("[t1][" + i + "]" + parse(ClassLayout.parseInstance(obj).toPrintable()));
                synchronized (obj) {
                    System.out.println("[t1][" + i + "]" + parse(ClassLayout.parseInstance(obj).toPrintable()));
                }
                System.out.println("[t1][" + i + "]" + parse(ClassLayout.parseInstance(obj).toPrintable()));
                list.add(obj);
            }
            System.out.println("========================>t1线程给批量上锁结束");
        }).start();
        new Thread(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int i = 0; i < num; i++) {
                TwoDemo obj = list.get(i);
                System.out.println("[t2][" + i + "]" + parse(ClassLayout.parseInstance(obj).toPrintable()));
                synchronized (obj) {
                    System.out.println("[t2][" + i + "]" + parse(ClassLayout.parseInstance(obj).toPrintable()));
                }
                System.out.println("[t2][" + i + "]" + parse(ClassLayout.parseInstance(obj).toPrintable()));
                list.add(obj);
            }
            System.out.println("========================>t2线程给批量上锁结束");
        }).start();
        new Thread(() -> {
            try {
                Thread.sleep(6000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int i = 0; i < num; i++) {
                TwoDemo obj = list.get(i);
                System.out.println("[t3][" + i + "]" + parse(ClassLayout.parseInstance(obj).toPrintable()));
                synchronized (obj) {
                    System.out.println("[t3][" + i + "]" + parse(ClassLayout.parseInstance(obj).toPrintable()));
                }
                System.out.println("[t3][" + i + "]" + parse(ClassLayout.parseInstance(obj).toPrintable()));
                list.add(obj);
            }
            System.out.println("========================>t3线程给批量上锁结束");
        }).start();
        
        try {
            Thread.sleep(10000); //主线程睡10秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程创建的对象:" + parse(ClassLayout.parseInstance(new TwoDemo()).toPrintable()));
    }
    

    下图是信息的显示介绍:信息

    下面输出结果:

    [t1][0]00000000 00000000 00000000 00000101	//默认开启偏向锁,但是没有线程id 
    [t1][0]00011001 11111111 01000000 00000101 	//持有锁后,还是偏向锁,但是修改为t1的线程id
    [t1][0]00011001 11111111 01000000 00000101 	//释放锁后,状态不变,以下都是批量给对象上锁
    [t1][1]00000000 00000000 00000000 00000101 
    [t1][1]00011001 11111111 01000000 00000101 
    [t1][1]00011001 11111111 01000000 00000101 
    //省略重复的信息 
    [t1][37]00000000 00000000 00000000 00000101 
    [t1][37]00011001 11111111 01000000 00000101 
    [t1][37]00011001 11111111 01000000 00000101 
    ========================>t1线程给批量上锁结束
    [t2][0]00011001 11111111 01000000 00000101 	//再让t2线程持有锁,此时获取的还是t1的状态
    [t2][0]00011011 01110110 11101111 00101000 	//此时撤销偏量级锁,升级为轻量级锁,前面改为线程的lock record地址信息
    [t2][0]00000000 00000000 00000000 00000001 	//释放锁后,对象转为无锁状态
    [t2][1]00011001 11111111 01000000 00000101 
    [t2][1]00011011 01110110 11101111 00101000 
    [t2][1]00000000 00000000 00000000 00000001 
    //省略重复的信息 
    [t2][18]00011001 11111111 01000000 00000101 
    [t2][18]00011011 01110110 11101111 00101000 
    [t2][18]00000000 00000000 00000000 00000001 
    [t2][19]00011001 11111111 01000000 00000101
    [t2][19]00011010 11100101 10100001 00000101  //从第20个对象开始,不再发生偏向锁撤销,而是将线程id改为t2的id
    [t2][19]00011010 11100101 10100001 00000101  //释放锁后,仍然是t2的信息,此时撤销偏向锁次数为19次
    [t2][20]00011001 11111111 01000000 00000101 
    [t2][20]00011010 11100101 10100001 00000101 
    [t2][20]00011010 11100101 10100001 00000101 
    //省略重复的信息 
    [t2][37]00011001 11111111 01000000 00000101 
    [t2][37]00011010 11100101 10100001 00000101 
    [t2][37]00011010 11100101 10100001 00000101 
    ========================>t2线程给批量上锁结束
    [t3][0]00000000 00000000 00000000 00000001 	//t3线程刚开始获取的还是集合中发生偏向锁撤销的对象,此时状态为无锁
    [t3][0]00011011 10000110 11101110 00001000 	//直接从无锁升级为轻量级锁
    [t3][0]00000000 00000000 00000000 00000001  //释放锁后,变为无锁状态,后面19个对象都是一样
    //省略重复的信息 
    [t3][18]00000000 00000000 00000000 00000001 
    [t3][18]00011011 10000110 11101110 00001000 
    [t3][18]00000000 00000000 00000000 00000001 
    [t3][19]00011010 11100101 10100001 00000101 //第20个对象开始,获取到的是t2线程的信息,此时为偏向锁状态
    [t3][19]00011011 10000110 11101110 00001000  //此时会撤销偏向锁,升级为轻量级锁
    [t3][19]00000000 00000000 00000000 00000001  //释放锁后,转为无锁状态
    //省略重复的信息  
    [t3][37]00011010 11100101 10100001 00000101 
    [t3][37]00011011 10000110 11101110 00001000 
    [t3][37]00000000 00000000 00000000 00000001 
    ========================>t3线程给批量上锁结束
    //t2和t3线程总共加起来发生了38次偏向锁撤销,此时主线程创建对象的时候仍然为默认的偏向锁
    主线程创建的对象:00000000 00000000 00000001 00000101
    

    此时把代码中num值改为39,使撤销偏向锁的次数达到39次,再查看主线程创建的对象信息:

    //省略以上的信息,发现此时主线程中新创建的对象状态就是无锁状态了。
    主线程创建的对象:00000000 00000000 00000000 00000001 
    

    结论当关闭了偏向锁延迟时,当偏向锁撤销次数达到39次,那么这个类之后新创建的对象不再是偏向锁状态,而是无锁状态,(之前已经创建过的对象仍然保持原来的状态,只是后面用到的时候才会更改状态)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值