关于jvm:偏向锁的批量重偏向与批量撤销机制

72次阅读

共计 3980 个字符,预计需要花费 10 分钟才能阅读完成。

前言

从网上看了很多批量重偏差与批量撤销的介绍,但都只知其一; 不知其二,本着钻研的精力,应用数据进行剖析批量重偏差与批量撤销的工作机制。

筹备

首先,要先晓得偏差锁的偏差锁机制,着重看下撤销机制。
而后,要晓得【批量重偏差与批量撤销】的钻研对象是 Class,即锁对象对应的 Class,而非锁对象自身。

// 钻研的是对象.class
synchronized(对象) {
}
// 并不是说的这种,这种不思考
synchronized(对象.class) {}

如对机制不理解,能够看下这个博客 https://blog.csdn.net/sinat_41832255/article/details/89309944

另外几个 jvm 参数:

-XX:BiasedLockingBulkRebiasThreshold = 20   // 默认偏差锁批量重偏差阈值
-XX:BiasedLockingBulkRevokeThreshold = 40   // 默认偏差锁批量撤销阈值
-XX:+UseBiasedLocking // 应用偏差锁,jdk6 之后默认开启
-XX:BiasedLockingStartupDelay = 0 // 提早偏差工夫, 默认不为 0,意思为 jvm 启动多少 ms 当前开启偏差锁机制(此处设为 0,不提早)

在这里,我把博客中的图拷过去,不便大家思考。

注释

咱们次要看下图中的“是否开启重偏差?”这个条件分支,来看下什么是重偏差?

重偏差字面了解就是从新偏差,那什么状况下会从新偏差呢

批量重偏差

咱们首先要晓得的是,锁对象 Class 刚开始的状况下,是没有开启重偏差的,意思就是“是否开启重偏差?”这个分支刚开始的时候是始终走 “否” 的,即会始终 撤销偏差锁 ,当达到 BiasedLockingBulkRebiasThreshold(20)次数的时候, 容许重偏差

上面咱们看下代码

public class TestBiasedLocking {public static void main(String[] args) throws DecoderException, InterruptedException {
        // 首先咱们创立一个 list,来寄存锁对象
        List<TestBiasedLocking> list = new LinkedList<>();
        
        // 线程 1
        new Thread(() -> {for (int i = 0; i < 50; i++) {TestBiasedLocking testBiasedLocking = new TestBiasedLocking();
                list.add(testBiasedLocking); // 新建锁对象
                synchronized (testBiasedLocking) {System.out.println("第" + (i + 1) + "次加锁 - 线程 1");
                    System.out.println(ClassLayout.parseInstance(testBiasedLocking).toPrintable()); // 打印对象头信息
                }
            }
        }, "线程 1").start();
        
        // 让线程 1 跑一会儿
        Thread.sleep(2000);
        
        // 线程 2
        new Thread(() -> {for (int i = 0; i < 30; i++) {TestBiasedLocking testBiasedLocking = list.get(i);
                synchronized (testBiasedLocking) {System.out.println("第" + (i + 1) + "次加锁 - 线程 2");
                    System.out.println(ClassLayout.parseInstance(testBiasedLocking).toPrintable()); // 打印对象头信息
                }
            }
        }, "线程 2").start();

        LockSupport.park();}
}

先解释下是什么意思,这是输入了三次对象信息,第一行就是对象头信息(分为 4 组,每组 8 位,查看形式为,组(左 - 右),位(右 - 左)),红框内的最初三位就是最低的三位,即 偏差锁(1bit)+ 锁标记(2bit),其余钻研下就明确了;

1)【线程 1】1-50 次加锁都是 101,即偏差锁状态;这个没什么问题,线程 1 首次加的锁,并且没有别的线程竞争,所以对象头是偏差锁状态,对应的 Thread Id 为线程 1.
2)【线程 2】1-19 加的锁都是轻量级锁,即前 19 次进行了偏差锁撤销,第 20 次执行了重偏差,线程 id 指向线程 2;

举个不太形象的例子。比方你结婚了,你就属于你老公的了,然而隔壁老王看上你了(线程 2 加锁),这个时候你是不能和他在一起的(不可重偏差),除非离婚(锁打消,降级为轻量级锁,不可再结婚,偏差锁不可逆);有一天,zf 统计发现某个地区离婚太频繁了,间接给他们一次换老公的机会(可重偏差),前提是这个机会只给老公不在家的(正在应用的锁对象曾经属于正在应用的线程);
上面咱们把例子带入那个数据中,当第 19 集体离婚后,zf 间接给要离婚的人一次换人的机会,所以第 20 集体就间接把结婚证上的人名换成老王了(Tread Id 间接指向线程 2)

批量撤销

起初 zf 发现,换过老公的这批人,离婚还是那么频繁(重偏差过的锁对象频繁 撤销偏差锁),搞我呢?都玩完!这个地区都别给我结婚了当前(该类下的锁对象都不反对偏差锁了,正在运行的也撤销)。

这就是批量撤销的含意,咱们代码来演示下

public class TestBiasedLocking {public static void main(String[] args) throws DecoderException, InterruptedException {
        // 首先咱们创立一个 list,来寄存锁对象
        List<TestBiasedLocking> list = new LinkedList<>();

        // 线程 1
        new Thread(() -> {for (int i = 0; i < 50; i++) {TestBiasedLocking testBiasedLocking = new TestBiasedLocking();
                list.add(testBiasedLocking); // 新建锁对象
                synchronized (testBiasedLocking) {System.out.println("第" + (i + 1) + "次加锁 - 线程 1");
                    System.out.println(ClassLayout.parseInstance(testBiasedLocking).toPrintable());
                }

            }
            LockSupport.park();}, "线程 1").start();

        // 让线程 1 跑一会儿
        Thread.sleep(2000);

        // 线程 2
        new Thread(() -> {for (int i = 0; i < 40; i++) {TestBiasedLocking testBiasedLocking = list.get(i);
                synchronized (testBiasedLocking) {System.out.println("第" + (i + 1) + "次加锁 - 线程 2");
                        System.out.println(ClassLayout.parseInstance(testBiasedLocking).toPrintable());

                }
            }
            LockSupport.park();}, "线程 2").start();

        // 让线程 2 跑一会儿
        Thread.sleep(2000);

        // 线程 3
        new Thread(() -> {for (int i = 20; i < 40; i++) {TestBiasedLocking testBiasedLocking = list.get(i);
                synchronized (testBiasedLocking) {System.out.println("第" + (i + 1) + "次加锁 - 线程 3");
                        System.out.println(ClassLayout.parseInstance(testBiasedLocking).toPrintable());
                }
            }
            LockSupport.park();}, "线程 3").start();

        // 让线程 3 跑一会儿
        Thread.sleep(2000);
        System.out.println("新出世的妹子");
        System.out.println(ClassLayout.parseInstance(new TestBiasedLocking()).toPrintable());
        LockSupport.park();}
    }

上面咱们用艰深的语言来解释下

1) 线程 1、1-50 都结婚了(有偏差的线程了)2) 线程 2、1-19 都离婚了(锁撤销),zf 看不行啊,间接结婚证改名吧,20-40 都换老公了(Thread Id 间接换了)// 达到 BiasedLockingBulkRebiasThreshold 次数
3) 线程 3、20-40 又离婚了(锁撤销),滚蛋,都玩完,谁都不能结婚,结婚的也都给我离婚(设置为不可偏差状态,正在运行的锁对象会被撤销)// 达到 BiasedLockingBulkRevokeThreshold 次数
4) 新出世的妹子,生下来就不让结婚。(new 进去就是轻量级锁)

二婚的人持续时间超过 -XX:BiasedLockingDecayTime=25000ms 的话,撤销次数清为 0,从新计算。

最初

最初我来解释下文章中呈现的举例

怎么判断只有一次换老公的机会的 (每个对象是怎么判断只有重偏差的一次机会的?)
答:对象头中偏差锁有个 Epoch,该对象对应的 Class 中也有这个字段,当可偏差的时候,Class 中的 Epoch+1,正在应用的锁对象 Epoch 也会 +1(老公在家,没有机会换老公),判断可重偏差的时候,Class.Epoch != 对象.Epoch,代表可重偏差。


欢送斧正。

正文完
 0