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

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参数:内存指针,预期值,期望值。从内存取值与预期值比较,如果相同则更改为期望值

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345