美文网首页JVM并发
synchronzied偏向锁的批量重偏向与撤销

synchronzied偏向锁的批量重偏向与撤销

作者: loveFXX | 来源:发表于2019-12-08 21:54 被阅读0次

    JVM在编译synchronzied时,会编译成monitorenter monitorexit指令,是一种JVM规范

    synchronzied锁的种类

    轻量锁:多个线程交替执行
    (一个线程A先拥有一把锁,然后另一个线程B过来争取锁。B持有的这个对象锁不立刻膨胀,先自旋一段时间,如果B自旋超时还没持有这个对象,则锁升级)
    重量锁:互斥
    偏向锁:对象初始化是可偏向状态

    开启了偏向延迟

    -XX:BiasedLockingStartupDelay=0
    在线程进入同步代码块之前(当对象第一次进入),首先判断这个对象是否可偏向即是JVM是否开启了偏向延迟,开启是可偏向状态,否则是无锁。可偏向状态升级膨胀为偏向锁,无锁升级为轻量锁。

    批量撤销重偏向

    以class为单位,为每个class维护一个偏向锁撤销计数器。每一次该class的对象发生偏向撤销操作是,该计数器+1,当这个值达到重偏向阈值(默认20)时,JVM就认为该class的偏向锁有问题,因此会进行批量重偏向。每个class对象也会有一个对应的epoch字段,每个处于偏向锁状态对象的mark word中也有该字段,其初始值为创建该对象时,class中的epoch值。每次发生批量重偏向时,就将该值+1,同时遍历JVM中所有线程的站,找到该class所有正处于加锁状态的偏向锁,将其epoch字段改为新值。下次获取锁时,发现当前对象的epoch值和class不相等,那就算当前已经偏向了其他线程,也不会执行撤销操作,而是直接通过CAS操作将其mark word的Thread Id改为当前线程ID
    CAS 之前线程id 当前线程id 期望退出状态(无锁状态) 失败将会膨胀

    线程复用线程id

    package com.thread;
    public class MyJolTest13 {
        static A a;
        static List<A> list = new ArrayList<>(  );
        public static void main(String[] args) throws Exception {
            Thread.sleep(5000);
            a = new A();
            System.out.println( "t1" );
            Thread t1 = new Thread(){
                public void run() {
                    synchronized (a) {
                        list.add( a );
                        System.out.println( ClassLayout.parseInstance( a ).toPrintable() );
    
                    }
                }
            };
            t1.start();
            t1.join();
    //      Thread tnull = new Thread( ){
    //          @Override
    //          public void run() {
    //              super.run();
    //          }
    //      };
    //      tnull.start();
    
            System.out.println( "t2" );
    //      System.out.println(ClassLayout.parseInstance( a ).toPrintable());
            Thread t2 = new Thread(){
                public void run() {
                        synchronized (a){
                                System.out.println("---------"+ClassLayout.parseInstance( a ).toPrintable());
                        }
                    }
    
            };
            t2.start();
    
        }
    
    }
    

    打印结果。示例代码开启空线程后,线程id不同。(这里仅仅是一种打印现象没有底层源码支持)


    image.png

    打印初始化信息

    -XX:+PrintFlagsInitial


    image.png

    BiasedLockingBulkRebiasThreshold 20 偏向锁批量重偏向阈值
    BiasedLockingBulkRevokeThreshold 40 偏向锁批量撤销阈值
    BiasedLockingStartupDelay 4000 偏向锁延时时间

    批量重偏向

    public class MyJolTest14 {
        static A a;
        // -XX:+PrintFlagsInitial
        static List<A> list = new ArrayList<>(  );
        public static void main(String[] args) throws Exception {
            Thread.sleep(5000);
            a = new A();
            System.out.println( "t1" );
            System.out.println( ClassLayout.parseInstance( a ).toPrintable() );
            Thread t1 = new Thread(){
                public void run() {
                    for (int i = 0; i <30 ; i++) {
                        A a = new A();
                        synchronized (a){
                            list.add( a );
                        }
    
                        if(i==3){
                            System.out.println( "t1"+ClassLayout.parseInstance( a ).toPrintable() );
                        }
                    }
                }
            };
            t1.start();
            t1.join();
            Thread tnull = new Thread( ){
                @Override
                public void run() {
                    super.run();
                }
            };
            tnull.start();
            System.out.println( "t2" );
            Thread t2 = new Thread(){
                A a15;
                int k=0;
                public void run() {
                    for (A a:list) {
                        synchronized (a){
    
                            if(k==18||k==19||k==29){
                                System.out.println(k+"---------"+ClassLayout.parseInstance( a ).toPrintable());
                            }
                            if(k==28){
                                a15=list.get( 15 );
                            }
                        }
                        k++;
                    }
                    synchronized (a15) {
                        System.out.println( "----15-----" + ClassLayout.parseInstance( a15 ).toPrintable() );
                                             //此时打印的是轻量级锁
                    }
    
                }
    
            };
            t2.start();
    
        }
    
    }
    

    代码解析:t1 线程实例化相同类A的多个对象并放入集合且使用synchronized关键字进行同步对象,由于休眠了5s所以集合中的对象都是偏向锁指向线程t1。t2对集合中对象进行synchronized同步执行,由于t1线程已经改变为偏向t1,所以到t2这里锁要升级,会多次撤销偏向锁升级为轻量锁。当到达阈值20时,jvm会对接下来的对象进行批量重偏向,所以接下来的对象都是偏向指向线程t2不再是轻量

    批量重偏向阈值20

    测试结果:
    代码中k=19时,是第20次。根据测试结果证明BiasedLockingBulkRebiasThreshold偏向锁批量重偏向的阈值是20。效果是20以后都是偏向锁直接偏向t2线程


    image.png
    image.png

    简单说明原理:对于a.class类,有一个计数器和初始epoch值(00)。当进入t2线程,前19个都是升级为轻量锁。当第20个时,此时计数器达到20,JVM认为这个类频繁升级有问题,将会对epoch值+1(01)。对于t1线程,如果对象还存活将存活的对象的epoch值赋值为新的epoch值(01)。对于t1线程已经失效的,在t2线程中重新偏向t2。对象的epoch值是根据class类中赋值的。前19个还是轻量级锁(synchronized (a15)打印可以证明)


    image.png

    批量撤销阈值40

    理论:假如t1线程创建100个对象a的集合。t2线程首先进行20次撤销,然后重偏向到t2,20之后直接使用if条件判断没有撤销直接偏向t2。t3线程过来,首先撤销偏向t2升级为轻量级锁,当再次达到20就不会重新偏向t3。剩余的直接批量膨胀轻量级锁


    image.png

    synchronized原理总结:

    synchronized锁的种类及对象头lock:
    偏向锁(101)、轻量级锁(000)、重量级锁(010)
    synchronized锁的膨胀过程:
    当线程访问同步代码块。首先查看当前锁状态是否是偏向锁(可偏向状态)
    1、如果是偏向锁:
    1.1、检查当前mark word中记录是否是当前线程id,如果是当前线程id,则获得偏向锁执行同步代码块。
    1.2、如果不是当前线程id,cas操作替换线程id,替换成功获得偏向锁(线程复用),替换失败锁撤销升级轻量锁(同一类对象多次撤销升级达到阈值20,则批量重偏向)
    2、升级轻量锁(纯理论可结合源码)
    升级轻量锁对于当前线程,分配栈帧锁记录lock_record(包含mark word和object-指向锁记录首地址),对象头mark word复制到线程栈帧的锁记录 mark word存储的是无锁的hashcode(里面有重入次数问题,涉及源码不深入)。
    3、重量级锁(纯理论可结合源码)
    CAS自旋达到一定次数升级为重量级锁(多个线程同时竞争锁时)
    重量级锁:存储在ObjectMonitor对象,里面有很多属性ContentionList、EntryList 、WaitSet、owner。当一个线程尝试获取锁时,如果该锁已经被占用,则该县城封装成ObjectWaiter对象插到ContentionList队列的对首,然后调用park挂起。该线程锁时方式会从ContentionList或EntryList挑一个唤醒。线程获得锁后调用Object的wait方法,则会加入到WaitSet集合中(当前锁或膨胀为重量级锁)
    CAS参数:内存指针,预期值,期望值。从内存取值与预期值比较,如果相同则更改为期望值

    相关文章

      网友评论

        本文标题:synchronzied偏向锁的批量重偏向与撤销

        本文链接:https://www.haomeiwen.com/subject/zvjegctx.html