并发系列三:证明分代年龄、无锁、偏向锁、轻量锁、重(chong)偏向、重(chong)轻量、重量锁

前言

  • 上篇文章咱们了解了synchronized关键字的常见用法、对象头以及证明了一个对象在无锁状态下的对象头markwork部分的前56位存储的是hashcode。接下来,咱们继续来根据对象头分别证明分代年龄为什么是15、无锁、偏向锁、轻量锁、重(chong)偏向、重(chong)轻量、重量锁,这些锁是真实存在的,咱们可以通过代码来重现。废话不多说,咱们一一来证明

一、证明分代年龄为什么为15

  • 大家都知道,在jvm中,若一个对象在survivor区经过了15次的young gc。当再进行一次young gc时,这个对象将会移动到老年代。那么为什么是15而不是16、17、18呢?这个问题就跟hashmap的初始容量为什么为16的原因有点相似,都涉及到对象的二进制。我们继续拿java对象头来说明,请看下图:

无锁状态下的对象头并发系列三:证明分代年龄、无锁、偏向锁、轻量锁、重(chong)偏向、重(chong)轻量、重量锁_第1张图片
由第一张图可知,分代年龄占用了4bit,想象一下,4bit能表示的最大数是什么?没错,就是所有的bit位都是1,即1111 。而二进制的1111转化成十进制后的值就是15啦。现在能明白分代年龄为什么是15了吧?

  • 利用此部分,咱们把图中描述的锁状态以表格的方式呈现出来

    锁状态 锁标识 备注
    无锁 001 对象头中使用baised_lock + lock 一共3bit来表示无锁和偏向锁的
    偏向锁 101 对象头中使用baised_lock + lock 一共3bit来表示无锁和偏向锁的
    轻量锁 00 只用到了lock标识位
    重量锁 10 只用到了lock标识位
    GC标志 11 只用到了lock标识位

二、证明对象处于无锁状态

  • 要证明这个很简单,直接创建一个object对象,并且使用jol打印出来对象头就能分析出,请细看如下代码及运行结果
  • 第一步:创建User.java

    package com.eugene.basic.concurrency.objectheader;
    
    public class User {
    }
  • 第二步:使用JOL API查看user对象的布局信息

    package com.eugene.basic.concurrency.objectheader;
    
    import org.openjdk.jol.info.ClassLayout;
    
    /**
     * 验证对象头hashCode信息
     */
    public class Valid {
    
        public static void main(String[] args) {
            User user = new User();
            System.out.println(ClassLayout.parseInstance(user).toPrintable());
        }
    }
  • 运行结果如下图所示:
    并发系列三:证明分代年龄、无锁、偏向锁、轻量锁、重(chong)偏向、重(chong)轻量、重量锁_第2张图片
    上篇文章说了,本人电脑的cpu存储内存是以小端模式存储的,即低位内存存储低位数据。所以咱们只需要看红色框框的第一行的数据。第一行数据看哪呢?看value部分,它的值为00000001。根据咱们的java对象头结构图可知,从左边开始数,第1个bit是unused部分、第2-5个bit是分代年龄部分、第6个bit是biased_lock偏向锁标识、第7-8个bit是lock标识。由上分析可知:最后两位的值为01,而01可能代表为无锁或者偏向锁,此时咱们再往前看一位,发现biased_lock位的值为0.因此最后三位值为001 ⇒ 证明user对象此时是无锁状态。

三、证明偏向锁

  • 证明偏向锁之前,咱们按下图操作,给jvm添加查看全局配置的参数:
    并发系列三:证明分代年龄、无锁、偏向锁、轻量锁、重(chong)偏向、重(chong)轻量、重量锁_第3张图片
    直接运行main方法,运行结果如下所示(由于篇幅问题,只截图了关键部分)
    并发系列三:证明分代年龄、无锁、偏向锁、轻量锁、重(chong)偏向、重(chong)轻量、重量锁_第4张图片
    由图中的-XX:BiasedLockingStartupDelay=4000配置可知,jvm会在启动虚拟机之后的4s后才会开启偏向锁功能。知道这个概念后,咱们再来科普下什么是偏向锁。
  • 所谓偏向锁:即当一把锁处于可偏向状态时,当有线程持有这把锁后,这把锁将偏向于这个线程。这里提到了可偏向状态,何为可偏向状态呢?可偏向状态是指在jvm开启可偏向功能后,new出来的一个对象它都是可偏向状态,即它的标识位为101,但是没有具体的偏向某一个线程。
  • 证明可偏向状态和偏向锁:

    添加如下代码并执行:
    public class Valid {
    
        public static void main(String[] args) throws InterruptedException {
            // 这里要注意, 一定要在创建对象之前睡眠,若我们先创建对象,可以想一想会发生什么情况!
            // 那肯定是不会启动偏向锁的功能呀,我们都知道加锁其实是给对象加了个标识
            // 如果我们在偏向锁功能未开启之前创建了对象,很抱歉,
            // jvm没有那么智能,后面不会去把这个对象改成可偏向状态(是偏向锁,但是没有偏向具体
            // 的线程)
            Thread.sleep(4100);
    
            System.out.println(ByteOrder.nativeOrder().toString());
            User user = new User();
            System.out.println("before lock");
            System.out.println(ClassLayout.parseInstance(user).toPrintable());
    
            synchronized (user) {
                System.out.println("lock ing");
                System.out.println(ClassLayout.parseInstance(user).toPrintable());
            }
    
            System.out.println("after lock");
            System.out.println(ClassLayout.parseInstance(user).toPrintable());
        }
    }
    > 查看运行结果

    并发系列三:证明分代年龄、无锁、偏向锁、轻量锁、重(chong)偏向、重(chong)轻量、重量锁_第5张图片

    四、证明一个对象调用了hashcode方法后无法再被标识为偏向锁,而是升级成轻量锁

  • 编写如下代码(相对于上述代码,仅在加锁前调用了对象的hashcode方法):

    public class Valid {
    
        public static void main(String[] args) throws InterruptedException {
            // 这里要注意, 一定要在创建对象之前睡眠,若我们先创建对象,可以想一想会发生什么情况!
            // 那肯定是不会启动偏向锁的功能呀,我们都知道加锁其实是给对象加了个标识
            // 如果我们在偏向锁功能未开启之前创建了对象,很抱歉,
            // jvm没有那么智能,后面不会去把这个对象改成可偏向状态(是偏向锁,但是没有偏向具体
            // 的线程)
            Thread.sleep(4100);
    
            System.out.println(ByteOrder.nativeOrder().toString());
            User user = new User();
            System.out.println("before lock");
            System.out.println(ClassLayout.parseInstance(user).toPrintable());
    
            System.out.println(user.hashCode());
    
            synchronized (user) {
                System.out.println("lock ing");
                System.out.println(ClassLayout.parseInstance(user).toPrintable());
            }
    
            System.out.println("after lock");
            System.out.println(ClassLayout.parseInstance(user).toPrintable());
        }
    }
    
  • 运行结果及分析
    并发系列三:证明分代年龄、无锁、偏向锁、轻量锁、重(chong)偏向、重(chong)轻量、重量锁_第6张图片

    五、证明轻量锁

  • 这里说下轻量锁的概念:若线程是交替执行的,即上一个线程执行完释放锁后下一个线程再获取锁。若在jvm未开启偏向锁的过程中,对对象进行加锁时,对象直接是轻量锁。
  • 撰写如下代码并执行:

    public class Valid {
    
        public static void main(String[] args) throws InterruptedException {
            System.out.println(ByteOrder.nativeOrder().toString());
    
            User user = new User();
            System.out.println("before lock");
            System.out.println(ClassLayout.parseInstance(user).toPrintable());
    
            synchronized (user) {
                System.out.println("lock ing");
                System.out.println(ClassLayout.parseInstance(user).toPrintable());
            }
    
            System.out.println("after lock");
            System.out.println(ClassLayout.parseInstance(user).toPrintable());
        }
    }
  • 运行结果如下
    并发系列三:证明分代年龄、无锁、偏向锁、轻量锁、重(chong)偏向、重(chong)轻量、重量锁_第7张图片

    六、证明偏向锁膨胀为轻量锁

  • 编写如下代码并执行

    public class Valid {
    
        public static void main(String[] args) throws InterruptedException {
            // 开启偏向锁功能
            Thread.sleep(4100);
            System.out.println(ByteOrder.nativeOrder().toString());
    
            User user = new User();
            System.out.println("before lock" + ClassLayout.parseInstance(user).toPrintable());
    
            synchronized (user) {
                System.out.println("lock ing" + ClassLayout.parseInstance(user).toPrintable());
            }
    
            System.out.println("after lock" + ClassLayout.parseInstance(user).toPrintable());
    
            // 开启线程来获取锁
            Thread t1 = new Thread(() -> {
                synchronized (user) {
                    System.out.println("other t1 thread get lock" + ClassLayout.parseInstance(user).toPrintable());
                }
            }, "t1");
            t1.start();
            // 等待t1执行完后再打印一次锁信息
            t1.join();
    
            System.out.println("after t1 thread release lock" + ClassLayout.parseInstance(user).toPrintable());
        }
    }
  • 运行结果及分析
    并发系列三:证明分代年龄、无锁、偏向锁、轻量锁、重(chong)偏向、重(chong)轻量、重量锁_第8张图片

    七、证明重(chong)偏向

  • 继续引用第三章:证明偏向锁的图
    并发系列三:证明分代年龄、无锁、偏向锁、轻量锁、重(chong)偏向、重(chong)轻量、重量锁_第9张图片
    我们关注intx BiasedLockingBulkRebiasThreshold = 20此配置。此配置说明整个偏向锁重偏向的阈值为20。ok,阈值咱们知道了,接下来说明下什么叫做重偏向
  • 所谓重偏向,按照字面意思来理解就是:锁的重偏向过程。但是大家都知道,锁的状态是不可逆的,当偏向锁被其他线程持有后就会膨胀成轻量锁了。但是,这里的重偏向是指批量重偏向。咱们先来看例子再来总结:
  • 编写如下类,并运行它:

    public class ReBiasedLock {
    
        static List locks = new ArrayList<>();
    
        static final int THREAD_COUNT = 19;
    
        public static void main(String[] args) throws InterruptedException {
            // 延迟4.1秒,等待jvm偏向锁功能开启
            Thread.sleep(4300);
    
            // 线程1中的锁全为偏向锁。
            Thread t1 = new Thread(() -> {
                for (int i = 0; i < THREAD_COUNT; i++) {
                    User lock = new User();
                    locks.add(lock);
                    synchronized (lock) {
                        System.out.println("线程1 第 " + (i + 1) + " 把锁");
                        System.out.println(ClassLayout.parseInstance(lock).toPrintable());
                        System.out.println("\n *********************************** \n");
                    }
    
                }
            }, "线程1");
    
            t1.start();
            // 等t1执行完
            t1.join();
    
            // 添加一个新线程,防止出现偏向锁的id重复的情况
            // 我也不知道为什么,只知道这样能解决这样的问题
            Thread tmp = new Thread(() -> {
                System.out.println(1);
            }, "tmp");
            tmp.start();
    
            new Thread(() -> {
                for (int i = 0; i < locks.size(); i++) {
                    User lock = locks.get(i);
                    synchronized (lock) {
                        System.out.println("线程2 第 " + (i + 1) + " 把锁");
                        System.out.println(ClassLayout.parseInstance(lock).toPrintable());
                        System.out.println("\n ==================================== \n");
                    }
                }
            }, "线程2").start();
    
        }
    }
    > **当线程数量THREAD_COUNT=19时**,第一个循环执行完毕后,线程list中的user对象全部为偏向锁,偏向于线程1。第二个线程执行完毕后,list中的user对象全部膨胀成轻量锁。这里查看下第一次和第二次循环的部分输出
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200528113134512.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2F2ZW5nZXJFdWc=,size_16,color_FFFFFF,t_70)

    并发系列三:证明分代年龄、无锁、偏向锁、轻量锁、重(chong)偏向、重(chong)轻量、重量锁_第10张图片

  • 我们测试另一种情况,把THREAD_COUNT改成25并执行它

    > **当线程数量THREAD_COUNT=25时**,第一个循环执行完毕后,同上,list中的user对象全部为偏向锁。第二个循环执行完后,前19把锁是轻量锁,从第20把锁开始,及其后面的所有的锁,都变成了偏向锁,重新偏向成了线程2。
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200528114057368.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2F2ZW5nZXJFdWc=,size_16,color_FFFFFF,t_70)

    并发系列三:证明分代年龄、无锁、偏向锁、轻量锁、重(chong)偏向、重(chong)轻量、重量锁_第11张图片

  • 结论:当同一类型的锁被同一个线程膨胀轻量锁的次数达到了20,那么会将后续的同一类型的锁统一重偏向到当前线程。

八、证明重(chong)轻量

  • 继续引用第三章:证明偏向锁的图
    并发系列三:证明分代年龄、无锁、偏向锁、轻量锁、重(chong)偏向、重(chong)轻量、重量锁_第12张图片
    我们关注intx BiasedLockingBulkRevokeThreshold = 40配置。此配置说明整个重轻量的阈值为40。ok,阈值咱们知道了,接下来说明下什么叫做重轻量
  • 重轻量概念:若同一类型的锁升级轻量锁的次数达到了40,此时就会将后面的锁都批量撤销为无锁状态,并膨胀到轻量锁
  • 咱们新增如下代码并运行它:

    public class ReLightweightLock {
    
        static List locks = new ArrayList<>();
    
        public static void main(String[] args) throws InterruptedException {
            System.out.println("Starting");
    
            // 延迟加载,让jvm开启偏向锁功能
            Thread.sleep(4400);

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 45; i++) {
                User lock = new User();
                locks.add(lock);
                synchronized (lock) {
                    // 不做任何事,可以确定45把锁全部变成了偏向锁
                }
            }
        }, "t1");
        t1.start();
        t1.join();

        // 打印第43把锁,已经是偏向锁了
        System.out.println("i = 42 \t" + ClassLayout.parseInstance(locks.get(42)).toPrintable());

        // 创建一个新线程睡眠2s,保证下面的代码先执行,保证重偏向时,不会出现线程ID重复的情况
        new Thread(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "tmp1").start();

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < locks.size(); i++) {
                User lock = locks.get(i);
                synchronized (lock) {
                    if (i == 10 || i == 21) {
                        // 输出第11和22个,看看分别是不是轻量锁和偏向锁
                        System.out.println("t2 i = " + i + "\t" + ClassLayout.parseInstance(lock).toPrintable());
                    }
                }
            }
        }, "t1");
        t2.start();
        t2.join();

        // 查看第11把锁对象,看看是不是20之前的锁也被重偏向了  --> 结果证明,只会对20以后的锁重偏向
        // 这里输出的是无锁状态,因为i= 10时,被线程2持有过,膨胀成轻量锁了,而轻量锁在释放锁后会变成无锁状态
        System.out.println("i = 10\t" + ClassLayout.parseInstance(locks.get(10)).toPrintable());

        // 查看第43把锁对象,看看是不是被批量重偏向了  --> 结果证明:是的
        System.out.println("i = 42\t" + ClassLayout.parseInstance(locks.get(42)).toPrintable());


        // 创建一个新线程睡眠2s,保证下面的代码先执行,保证重偏向时,不会出现线程ID重复的情况
        new Thread(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "tmp2").start();

        Thread t3 = new Thread(() -> {
            for (int i = 0; i < locks.size(); i++) {
                User lock = locks.get(i);
                synchronized (lock) {
                    if (i == 10 || i == 21 || i == 40) {
                        // 输出第11和22个,看看是不是都为轻量锁
                        // ---> 结果证明:都为轻量锁
                        // i == 10为轻量锁,我们都能理解,因为偏向锁被其他线程持有了,当然膨胀为轻量锁了
                        // 可是i == 21不应该为偏向锁么?(超过了重偏向的阈值)
                        // ==> 这里不是重偏向了,因为user类型的锁升级为轻量锁的次数达到了40(线程2升级了20次),
                        // 所以jvm直接做了重轻量的操作,把后面所有的锁都变成轻量锁了
                        // 所以i == 21应该是轻量锁
                        // i == 40同样也是轻量锁
                        System.out.println("t3 i = " + i + "\t" + ClassLayout.parseInstance(lock).toPrintable());
                    }
                }
            }
        }, "t3");
        t3.start();
        t3.join();

        // 此时是无锁状态,因为线程3进行批量重轻量了,而它释放了锁,所以是无锁状态
        System.out.println("main i = 40 \t" + ClassLayout.parseInstance(locks.get(40)).toPrintable());

    }
}
```
分析结果在注释上已经有了,可以根据注释信息和下面的运行结果来做比对
```txt
Starting
i = 42     com.eugene.basic.concurrency.objectheader.User object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 f8 a1 29 (00000101 11111000 10100001 00101001) (698480645)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t2 i = 10    com.eugene.basic.concurrency.objectheader.User object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           e8 f5 69 2a (11101000 11110101 01101001 00101010) (711587304)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t2 i = 21    com.eugene.basic.concurrency.objectheader.User object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 59 a8 29 (00000101 01011001 10101000 00101001) (698898693)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

i = 10    com.eugene.basic.concurrency.objectheader.User object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

i = 42    com.eugene.basic.concurrency.objectheader.User object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 59 a8 29 (00000101 01011001 10101000 00101001) (698898693)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t3 i = 10    com.eugene.basic.concurrency.objectheader.User object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           c8 ee 89 2a (11001000 11101110 10001001 00101010) (713682632)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t3 i = 21    com.eugene.basic.concurrency.objectheader.User object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           c8 ee 89 2a (11001000 11101110 10001001 00101010) (713682632)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

t3 i = 40    com.eugene.basic.concurrency.objectheader.User object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           c8 ee 89 2a (11001000 11101110 10001001 00101010) (713682632)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

main i = 40     com.eugene.basic.concurrency.objectheader.User object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total


Process finished with exit code 0

```

九、证明重量锁

  • 重量锁概念:多个线程存在激烈的竞争时,锁会膨胀成重量锁,且不可逆!
  • 典型案例:生产者消费者模型:

    public class ValidSynchronized {
    
        static Object lock = new Object();
    
        static volatile LinkedList queue = new LinkedList<>();
    
        public static void main(String[] args) throws InterruptedException {
            System.out.println("before lock");
            System.out.println(ClassLayout.parseInstance(lock).toPrintable());
    
            Consumer consumer = new Consumer();
            Producer producer = new Producer();
    
            consumer.start();
            producer.start();
    
            Thread.sleep(500);
            consumer.interrupt();
            producer.interrupt();
    
            // 睡眠3s ==> 目的是为了让锁自己释放,防止在释放过程中打印锁的状态出现重量锁的情况
            Thread.sleep(3000);
            System.out.println("after lock");
            System.out.println(ClassLayout.parseInstance(lock).toPrintable());
        }
    }
    
    class Producer extends Thread {
    
        @Override
        public void run() {
            while (!isInterrupted()) {
                synchronized (ValidSynchronized.lock) {
                    System.out.println("lock ing");
                    System.out.println(ClassLayout.parseInstance(ValidSynchronized.lock).toPrintable());
                    String message = UUID.randomUUID().toString();
                    System.out.println("生产者生产消息:" + message);
                    ValidSynchronized.queue.offer(message);
                    try {
                        // 生产者自己wait,目的是释放锁
                        ValidSynchronized.lock.notify();
                        ValidSynchronized.lock.wait();
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        this.interrupt();
                    }
                }
            }
        }
    }
    
    class Consumer extends Thread {
    
        @Override
        public void run() {
            while (!isInterrupted()) {
                synchronized (ValidSynchronized.lock) {
                    if (ValidSynchronized.queue.size() == 0) {
                        try {
                            ValidSynchronized.lock.wait();
                            ValidSynchronized.lock.notify();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    String message = ValidSynchronized.queue.pollLast();
                    System.out.println("消费者消费消息:" + message);
    
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        this.interrupt();
                    }
                }
            }
        }
    }
    运行结果:

    并发系列三:证明分代年龄、无锁、偏向锁、轻量锁、重(chong)偏向、重(chong)轻量、重量锁_第13张图片

    十、证明调用wait方法后,锁会升级为重量锁

  • 运行如下代码:

    public class ValidWait {
    
        public static void main(String[] args) throws InterruptedException {
            Thread.sleep(4100);
    
            final User user = new User();
    
            System.out.println("before lock");
            System.out.println(ClassLayout.parseInstance(user).toPrintable());
    
            Thread t1 = new Thread(() -> {
                synchronized (user) {
                    System.out.println("lock ing");
                    System.out.println("before wait");
                    System.out.println(ClassLayout.parseInstance(user).toPrintable());
    
                    try {
                        user.wait();
                        System.out.println("after wait");
                        System.out.println(ClassLayout.parseInstance(user).toPrintable());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
    
            }, "t1");
            t1.start();
    
            // 主线程睡眠3s后,唤醒t1线程
            Thread.sleep(3000);
            System.out.println("主线程查看锁,变成了重量锁");
            System.out.println(ClassLayout.parseInstance(user).toPrintable());
        }
    }
  • 运行结果如下
    并发系列三:证明分代年龄、无锁、偏向锁、轻量锁、重(chong)偏向、重(chong)轻量、重量锁_第14张图片

    十一、总结

  • 偏向锁和hashcode是互斥的,只能存在一个
  • jvm默认对偏向锁功能是延迟加载的,大概时间为4s钟,可以添加JVM参数: -XX:BiasedLockingStartupDelay=0来设置延迟时间为0。偏向锁的延迟加载关闭后,基本上所有的锁都会为可偏向状态,即mark word为101,但是它还没有具体偏向的线程信息
  • 偏向锁退出同步块后依然也是偏向锁
  • 重量级锁之所以重量就是因为状态不停的切换,最终映射到代码层面就是不停的调用操作系统函数(最终会调用到jvm的mutex类)
  • 调用锁对象的wait方法时,当前锁对象会立马升级为重量级锁
  • 偏向锁只要被其他线程拿到了,此时偏向锁会膨胀。膨胀为轻量锁
  • 并发模块对应github地址:传送门
  • I am a slow walker, but I never walk backwards.

你可能感兴趣的:(java并发)