批量重偏向和批量撤销理解

简介

本文记录一下自己看b站juc视频的一点疑惑。视频地址
疑惑主要是下面两个问题

重偏向的时候,撤销次数会增加吗? 不会

一个被重偏向完的对象,还能被第二次重偏向吗? 能(本来写的是不能,经评论指正后更改)
在一次批量重偏向中,一个被重偏向的对象,还能被第二次重偏向吗? 不能
注意上面两者的区别,文中描述的是不能的情况。
能的情况可以看评论区的解释。

不清楚的朋友可以往下看一看,应该能有所收获。

实验代码

package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;

import java.util.Vector;
import java.util.concurrent.locks.LockSupport;

@Slf4j(topic = "c.TestBiased")
public class TestBiased {

    static Thread t1,t2,t3;

    public static void main(String[] args) throws InterruptedException {
        test4();
    }
    private static void test4() throws InterruptedException {
        Vector<Dog> list = new Vector<>();

        int loopNumber = 39;
        t1 = new Thread(() -> {
            for (int i = 0; i < loopNumber; i++) {
                Dog d = new Dog();
                list.add(d);
                synchronized (d) {
                    log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
                }
            }
            LockSupport.unpark(t2);
        }, "t1");
        t1.start();

        t2 = new Thread(() -> {
            LockSupport.park();
            log.debug("===============> ");
            for (int i = 0; i < loopNumber; i++) {
                Dog d = list.get(i);
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
                
                synchronized (d) {
                    log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
                }
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
            }
            LockSupport.unpark(t3);
        }, "t2");
        t2.start();

        t3 = new Thread(() -> {
            LockSupport.park();
            log.debug("===============> ");
            for (int i = 0; i < loopNumber; i++) {
                Dog d = list.get(i);
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
                synchronized (d) {
                    log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
                }
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
            }
        }, "t3");
        t3.start();

        t3.join();
        log.debug(ClassLayout.parseInstance(new Dog()).toPrintable());
    }
}

class Dog {

}

整体逻辑和代码执行顺序:

  1. 线程t1创建39个Dog对象锁,并且都synchronized一遍,这样它们的Mark Wokd就都写了t1。
  2. 线程t2对39个对象锁又synchronized一遍,这样前19个(下标0-18)就被撤销成轻量级锁了,synchronized第20个锁时,进入批量重偏向状态,第20-39(下标19-38)重偏向到t2。
  3. 现在问题来了,线程t3中synchronized前19个没什么好说的,就是无锁变成轻量级锁。关键是20-39个,问一下自己,为什么此时不再是重偏向,而是撤销锁了?

分析

从上一部分的问题为起点开始分析:为什么t3中synchronized第20-39个锁对象时,不再是重偏向了。

如果loopNumber是40的话,可能很多人会觉得是因为t2中执行了40次synchronized,所以达到了批量撤销操作。这么想的朋友可以做个实验:把loopNumber改成40,然后在t3运行前创建一个新的Dog对象,你会发现Dog仍旧是偏向锁(101),而不是无锁(001)。这就说明重偏向是不会增加撤销数量的!!

那现在我们可以肯定t3中synchronized第20个对象时,撤销数刚好是20,也就是批量重偏向状态,并且此时t2已经释放了所有的锁,为什么t3不能执行重偏向呢?

t2 synchronized流程

我们来仔细分析一下t2中第1-39(下标0-38)个Dog锁对象的synchronized过程,就能理解了。

  1. 第1-19:只说是偏向锁的情况,先进行一系列判断(是不是偏向当前线程,偏向模式关闭的话撤销偏向锁,能不能重偏向),发现上述判断都不成立(偏向t1,偏向模式没有关闭,不能重偏向),进入锁升级。在锁升级这一步里,会把撤销次数+1,执行单次撤销锁操作。
  2. 第20:跟1中一样进入锁升级,把撤销次数+1,此时撤销次数==20,就执行批量重定向操作。具体如下:1.类Dog的epoch+1(00-01);2.把类Dog的所有处在加锁状态的锁对象的epoch更新为类Dog的epoch;3. 执行重偏向操作。
  3. 第21-39:先执行1中的一系列判断,此时发现可以进行重偏向,(重偏向的条件就是锁对象的epoch!=类的epoch),那就直接重偏向了,重偏向时会把锁对象epoch更新为类的epoch。不会进入锁升级,也不会+撤销次数了!
t3 synchronized流程

前19个就不用说了,在t2中都变成轻量级锁了。关键看第20-39,此时它们都是偏向锁,那也会执行分析t2时提到的一系列判断,但这时就不满足重偏向条件了,因为在t2的重偏向执行完后,20-38锁对象的epoch和类的epoch就一样了。那就会进入锁升级的流程,也就是20次撤销操作。

到此为止就能解释所有的问题了。最后简单说一下epoch。

epoch

epoch是Mark Word中的一部分,只存在于偏向锁状态,占2位。锁对应的类同样用2位存了epoch。

epoch的作用就是标记偏向的合法性,说通俗点,就是看这个偏向锁有没有其他线程正在用。如果不用epoch,那每次想要重偏向,都得去遍历所有的线程栈看看有没有其他线程在用。

再完整解释一遍:每次撤销数量刚到20的时候,锁的类epoch都会+1,并且更新加锁状态的同类锁对象,那么那些不加锁的锁对象epoch就和类的epoch不一样了,那就可以知道哪些偏向锁是空闲的了。

epoch只有两位,那肯定会循环,但不会影响准确性。

参考

死磕Synchronized底层实现–偏向锁

应该是偏向锁论文的翻译

论文

  • 9
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值