Java锁synchronized关键字学习系列之批量重偏向和批量撤销

Posted c.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java锁synchronized关键字学习系列之批量重偏向和批量撤销相关的知识,希望对你有一定的参考价值。

Java锁synchronized关键字学习系列之批量重偏向和批量撤销

这个系列的上一篇博文我们讲到了《Java锁synchronized关键字学习系列之偏向锁》

我们在讲偏向锁加锁的时候讲到了其中一步会判断是否开启重偏向,那到底啥事重偏向?为啥叫是否开启重偏向,说明这个重偏向机制并不是一直都是开启的。 今我们就来研究一下。

在这里插入图片描述

Java锁的重偏向机制

我们知道,当我们使用synchronized关键字的时候,一个对象如果只是被一个线程访问,没有任何竞争的情况下就加偏向锁。如果之后出现第二个线程访问这个对象a的时候,此时锁都会升级为轻量锁(不考虑重偏向的情况),并且锁升级过程不可逆。

但是如果有很多对象,这些对象同属于一个类(假设是类A)被线程1访问并加偏向锁,之后线程2来访问这些对象(不考虑竞争情况),在通过CAS操作把这些锁升级为轻量锁,会是一个很耗时的操作。

JVM对此作了优化:
当对象数量超过某个阈值时(默认20, jvm启动时加参数-XX:+PrintFlagsFinal可以打印这个阈值 ),Java会对超过的对象作批量重偏向线程2,此时前20个对象是轻量锁,
后面的对象都是偏向锁,且偏向线程2。

那我们看看这个默认的阈值吧,我这里使用的是java 11的版本,打印如下:

$ java -XX:+PrintFlagsInitial | grep -i biased
     intx BiasedLockingBulkRebiasThreshold         = 20                                        {product} {default}  // 默认偏向锁批量重偏向阈值
     intx BiasedLockingBulkRevokeThreshold         = 40                                        {product} {default} // 默认偏向锁批量撤销阈值
     intx BiasedLockingDecayTime                   = 25000                                     {product} {default} //距上次批量重偏向25秒内,撤销计数达到40,就会发生批量撤销。每隔(>=)25秒,会重置在[20, 40)内的计数,这意味着可以发生多次批量重偏向。
     intx BiasedLockingStartupDelay                = 0                                         {product} {default}  // 延迟偏向时间, 默认不为0,意思为jvm启动多少ms以后开启偏向锁机制(此处设为0,不延迟)
     bool UseBiasedLocking                         = true                                      {product} {default} // 使用偏向锁,jdk6之后默认开启

我们来用代码验证一下吧:

     /**
     * 批量重偏向
     */
    public static void main(String[] args) throws Exception {


        List<A> list = new ArrayList<>();
        //生成40个A的实例
        for (int i = 0; i < 40; i++) {
            list.add(new A());
        }

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                A a = list.get(i);
                synchronized (a) {
                    if (i == 1 || i == 19) {
                        System.out.println("t1 lock " + i);
                        //打印对象头
                        System.out.println(ClassLayout.parseInstance(a).toPrintable());//偏向锁
                    }
                }
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                A a = list.get(i);
                synchronized (a) {
                    if (i == 1 || i == 19) {//i<19的时候轻量锁, i>=19的时候是偏向锁
                        System.out.println("t2 lock " + i);
                        System.out.println(ClassLayout.parseInstance(a).toPrintable());
                    }
                }
            }
        });


        t1.start();
        t1.join();//通过join是t1结束后再启动t2,避免竞争
        t2.start();
        t2.join();
    }

我们来看打印的结果:

t1 lock 1
org.example.A object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           05 88 2d 71 (00000101 10001000 00101101 01110001) (1898809349)
      4     4           (object header)                           5c 02 00 00 (01011100 00000010 00000000 00000000) (604)
      8     4           (object header)                           48 72 06 00 (01001000 01110010 00000110 00000000) (422472)
     12     1   boolean A.flag                                    false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

t1 lock 19
org.example.A object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           05 88 2d 71 (00000101 10001000 00101101 01110001) (1898809349)
      4     4           (object header)                           5c 02 00 00 (01011100 00000010 00000000 00000000) (604)
      8     4           (object header)                           48 72 06 00 (01001000 01110010 00000110 00000000) (422472)
     12     1   boolean A.flag                                    false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

t2 lock 1
org.example.A object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           b8 eb 3f f6 (10111000 11101011 00111111 11110110) (-163583048)
      4     4           (object header)                           98 00 00 00 (10011000 00000000 00000000 00000000) (152)
      8     4           (object header)                           48 72 06 00 (01001000 01110010 00000110 00000000) (422472)
     12     1   boolean A.flag                                    false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

t2 lock 19
org.example.A object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           05 b1 5d 71 (00000101 10110001 01011101 01110001) (1901965573)
      4     4           (object header)                           5c 02 00 00 (01011100 00000010 00000000 00000000) (604)
      8     4           (object header)                           48 72 06 00 (01001000 01110010 00000110 00000000) (422472)
     12     1   boolean A.flag                                    false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total


Process finished with exit code 0

从打印的结果我们可以看出来,t1 lock 1, t1 lock 19 都是偏向锁,然后t2 lock 1打印出来的也是轻量级锁,所以这个时候已经从偏向锁升级到了轻量级锁,但是到t2 lock 19可以看到又是偏向锁了。说明在第20次的时候发生了批量重偏向了。

Java锁的批量撤销机制

讲完了批量重偏向,我们再聊聊这个批量撤销是啥?

前面说到了批量重偏向就是如果一个类的大量对象被一个线程T1执行了同步操作,也就是大量对象先偏向了T1,T1同步结束后,另一个线程也将这些对象作为锁对象进行操作,会导偏向锁重偏向的操作。

那批量撤销就是当一个偏向锁如果撤销次数到达40的时候就认为这个对象设计的有问题;那么JVM会把这个对象所对应的类所有的对象都撤销偏向锁;并且新实例化的对象也是不可偏向的。

我们来看示例代码:

/**
     * 批量撤销
     */
    public static void main(String[] args) throws Exception {


        List<A> list = new ArrayList<>();
        //生成40个A的实例
        for (int i = 0; i < 40; i++) {
            list.add(new A());
        }

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                A a = list.get(i);
                synchronized (a) {
                    if (i == 1 || i == 19) {
                        System.out.println("t1 lock " + i);
                        //打印对象头
                        System.out.println(ClassLayout.parseInstance(a).toPrintable());//偏向锁
                    }
                }
            }
        },"线程1");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 40; i++) {
                A a = list.get(i);
                synchronized (a) {
                    if (i == 1 || i == 19 || i == 39) {//i<19的时候轻量锁, i>=19的时候是偏向锁
                        System.out.println("t2 lock " + i);
                        System.out.println(ClassLayout.parseInstance(a).toPrintable());
                    }
                }
            }
        },"线程2");

        Thread t3 = new Thread(() -> {
            for (int i = 19; i < 40; i++) {
                A a = list.get(i);
                synchronized (a) {
                    if (i == 19 || i == 39) {
                        System.out.println("t3 lock " + i);
                        System.out.println(ClassLayout.parseInstance(a).toPrintable()); //线程3后面20个对象从重偏向之后又升级为轻量级锁
                    }
                }
            }
        },"线程3");


        t1.start();
        t1.join();//通过join是t1结束后再启动t2,避免竞争

        t2.start();
        t2.join();

        t3.start();
        t3.join();

        System.out.println("===========");
        System.out.println(ClassLayout.parseInstance(new A()).toPrintable());

    }
t1 lock 1
org.example.A object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           05 98 5d eb (00000101 10011000 01011101 11101011) (-346187771)
      4     4           (object header)                           e1 01 00 00 (11100001 00000001 00000000 00000000) (481)
      8     4           (object header)                           48 72 06 00 (01001000 01110010 00000110 00000000) (422472)
     12     1   boolean A.flag                                    false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

t1 lock 19
org.example.A object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           05 98 5d eb (00000101 10011000 01011101 11101011) (-346187771)
      4     4           (object header)                           e1 01 00 00 (11100001 00000001 00000000 00000000) (481)
      8     4           (object header)                           48 72 06 00 (01001000 01110010 00000110 00000000) (422472)
     12     1   boolean A.flag                                    false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

t2 lock 1
org.example.A object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           98 ee 5f 05 (10011000 11101110 01011111 00000101) (90173080)
      4     4           (object header)                           13 00 00 00 (00010011 00000000 00000000 00000000) (19)
      8     4           (object header)                           48 72 06 00 (01001000 01110010 00000110 00000000) (422472)
     12     1   boolean A.flag                                    false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

t2 lock 19
org.example.A object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           05 e9 7c eb (00000101 11101001 01111100 11101011) (-344135419)
      4     4           (object header)                           e1 01 00 00 (11100001 00000001 00000000 00000000) (481)
      8     4           (object header)                           48 72 06 00 (01001000 01110010 00000110 00000000) (422472)
     12     1   boolean A.flag                                    false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

t2 lock 39
org.example.A object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           05 e9 7c eb (00000101 11101001 01111100 11101011) (-344135419)
      4     4           (object header)                           e1 01 00 00 (11100001 00000001 00000000 00000000) (481)
      8     4           (object header)                           48 72 06 00 (01001000 01110010 00000110 00000000) (422472)
     12     1   boolean A.flag                                    false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

t3 lock 19
org.example.A object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           d8 f4 4f 05 (11011000 11110100 01001111 00000101) (89126104)
      4     4           (object header)                           13 00 00 00 (00010011 00000000 00000000 00000000) (19)
      8     4           (object header)                           48 72 06 00 (01001000 01110010 00000110 00000000) (422472)
     12     1   boolean A.flag                                    false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

t3 lock 39
org.example.A object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           d8 f4 4f 05 (11011000 11110100 01001111 00000101) (89126104)
      4     4           (object header)                           13 00 00 00 (00010011 00000000 00000000 00000000) (19)
      8     4           (object header)                           48 72 06 00 (01001000 01110010 00000110 00000000) (422472)
     12     1   boolean A.flag                                    false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

===========
org.example.A 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)                           48 72 06 00 (01001000 01110010 00000110 00000000) (422472)
     12     1   boolean A.flag                                    false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total


Process finished with exit code 0

可以看到一开始线程t1 40个对象全部加上了偏向锁,然后t2线程对这40个对象中的前20个对象进行了锁撤销,升级成了轻量级锁,然后对后面20个对象进行了重新偏向。最后t3线程对这后面20个对象又进行了锁撤销,升级成了轻量级锁。所以在25000ms范围锁撤销次数达到了40次。那么JVM会把这个对象所对应的类所有的对象都撤销偏向锁;并且新实例化的对象也是不可偏向的。所以最后新new出来的这个A对象,的对象头打印出来是无锁的,也就是不可偏向的。

参考

偏向锁的【批量重偏向与批量撤销】机制

Java锁的批量重偏向

synchronized批量重偏向与批量撤销

源代码

https://gitee.com/cckevincyh/java_synchronized_learning

以上是关于Java锁synchronized关键字学习系列之批量重偏向和批量撤销的主要内容,如果未能解决你的问题,请参考以下文章

Java锁synchronized关键字学习系列之重量级锁

Java锁synchronized关键字学习系列之批量重偏向和批量撤销

Java锁synchronized关键字学习系列之轻量级锁

Java锁synchronized关键字学习系列之轻量级锁升级

Java多线程系列---“基础篇”04之 synchronized关键字

Java基础加强之并发synchronized关键字