synchronized关键字的底层原理(锁升级,对象头)

synchronized根据竞争的激烈程度不同,实际上会经历如下几个过程:
无锁 --> 偏向锁(偏向锁实际上需要先获得轻量级锁,然后在锁重入时才会执行偏向锁优化) --> 轻量级锁(CAS设置markword+自旋) --> 重量级锁(OS层面,在升级为重量级锁时,若在多核cpu上,会出尝试多次自旋,若还是获取不到锁,才就会膨胀为重量级锁)


其中无锁,偏向锁,轻量级锁都是JVM层面所做的工作; 而重量级锁是OS层面的,这就涉及到用户态到内核态的转换.
轻量级锁连续自旋等待超过一定次数时(JVM默认设置为10次),为了避免CPU占用过高,会升级成重量级锁, 对于重量级锁, 在CPU层面是通过CAS指令来实现的.

synchronized在JVM层面是通过设置对象头来实现上述的锁升级/锁优化的,针对64bit的JVM,其对象头中的mark word格式如下:

64bit JVM的对象头实际上分为两部分,即Mark Word和Class Pointer, Mark Word占8字节,Class Pointer占4字节.

了解完以上理论知识,下面来编码验证,这里使用org.openjdk.jol库来解析类对象,不过org.openjdk.jol解析出的数据格式不太友好,特别对于小端机器来说,其二进制是反过来的(基本上x86 CPU以小端为主),这里我封装了解析对象头的函数:

 /**
     * 获得二进制数据
     * @param o
     * @return
     */
    public static String getObjectHeader(Object o){
        ByteOrder order=ByteOrder.nativeOrder();//字节序
        String table=ClassLayout.parseInstance(o).toPrintable();
        Pattern p=Pattern.compile("(0|1){8}");
        Matcher matcher=p.matcher(table);
        List<String> header=new ArrayList<>();
        while(matcher.find()){
            header.add(matcher.group());
        }
        //小端机器,需要反过来遍历
        StringBuilder sb=new StringBuilder();
        if(order.equals(ByteOrder.LITTLE_ENDIAN)){
            Collections.reverse(header);
        }
        for(String s:header){
            sb.append(s).append(" ");
        }
        return sb.toString().trim();
    }

    /**
     * 针对64bit jvm的解析对象头函数
     * 在64bit jvm中,对象头有两个部分: Mark Word和Class Pointer, Mark Word占8字节,Class Pointer占4字节
     * @param s 对象头的二进制形式字符串(每8位,使用一个空格分开)
     */
    public static void parseObjectHeader(String s){
        String[] tmp=s.split(" ");
        System.out.print("Class Pointer: ");
        for(int i=0;i<4;++i){
            System.out.print(tmp[i]+" ");
        }
        System.out.println("\nMark Word:");
        if(tmp[11].charAt(5)=='0'&&tmp[11].substring(6).equals("01")){//0 01无锁状态,不考虑GC标记的情况
            //notice: 无锁情况下mark word的结构: unused(25bit) + hashcode(31bit) + unused(1bit) + age(4bit) + biased_lock_flag(1bit) + lock_type(2bit)
            //      hashcode只需要31bit的原因是: hashcode只能大于等于0,省去了负数范围,所以使用31bit就可以存储
            System.out.print("\thashcode (31bit): ");
            System.out.print(tmp[7].substring(1)+" ");
            for(int i=8;i<11;++i) System.out.print(tmp[i]+" ");
            System.out.println();
        }else if(tmp[11].charAt(5)=='1'&&tmp[11].substring(6).equals("01")){//1 01,即偏向锁的情况
            //notice: 对象处于偏向锁的情况,其结构为: ThreadID(54bit) + epoch(2bit) + unused(1bit) + age(4bit) + biased_lock_flag(1bit) + lock_type(2bit)
            //      这里的ThreadID是持有偏向锁的线程ID, epoch: 一个偏向锁的时间戳,用于偏向锁的优化
            System.out.print("\tThreadID(54bit): ");
            for(int i=4;i<10;++i) System.out.print(tmp[i]+" ");
            System.out.println(tmp[10].substring(0,6));
            System.out.println("\tepoch: "+tmp[10].substring(6));
        }else{//轻量级锁或重量级锁的情况,不考虑GC标记的情况
            //notice: JavaThread*(62bit,include zero padding) + lock_type(2bit)
            //      此时JavaThread*指向的是 栈中锁记录/重量级锁的monitor
            System.out.print("\tjavaThread*(62bit,include zero padding): ");
            for(int i=4;i<11;++i) System.out.print(tmp[i]+" ");
            System.out.println(tmp[11].substring(0,6));
            System.out.println("\tLockFlag (2bit): "+tmp[11].substring(6));
            System.out.println();
            return;
        }
        System.out.println("\tage (4bit): "+tmp[11].substring(1,5));
        System.out.println("\tbiasedLockFlag (1bit): "+tmp[11].charAt(5));
        System.out.println("\tLockFlag (2bit): "+tmp[11].substring(6));

        System.out.println();
    }

 

定义一个对象:

    static class Obj{
        int i=0;
    }

下面来验证,注意一点,测试synchronized锁升级时,若要立即查看测试情况,需要禁止偏向锁延迟: -XX:BiasedLockingStartupDelay=0; 开启偏向锁: -XX:+UseBiasedLocking=0, 不过从jdk6开始就默认开启了,不开启的话就是先使用轻量级锁.

测试偏向锁:

    @Test
    public void testBiasedLock(){
        //需要禁止偏向锁延迟: -XX:BiasedLockingStartupDelay=0
        Obj o=new Obj();
        parseObjectHeader(getObjectHeader(o));
        synchronized (o){
            parseObjectHeader(getObjectHeader(o));
        }
    }

输出:

Class Pointer: 11111000 00000001 10100001 10100100 
Mark Word:
	ThreadID(54bit): 00000000 00000000 00000000 00000000 00000000 00000000 000000
	epoch: 00
	age (4bit): 0000
	biasedLockFlag (1bit): 1
	LockFlag (2bit): 01

Class Pointer: 11111000 00000001 10100001 10100100 
Mark Word:
	ThreadID(54bit): 00000000 00000000 00000000 00000000 00000011 00110111 100110
	epoch: 00
	age (4bit): 0000
	biasedLockFlag (1bit): 1
	LockFlag (2bit): 01

可以看出,偏向锁标志位为1,锁标志位01,即处于偏向锁状态,即使下一步使用了synchronized,仍然处于偏向锁状态

 

测试轻量级锁:

    @Test
    public void testLightLock(){
        //需要禁止偏向锁延迟: -XX:BiasedLockingStartupDelay=0
        Obj o=new Obj();
        parseObjectHeader(getObjectHeader(o));
        //升级为轻量级锁,因为下面的线程又占用了o,注意是上面先执行完后,才开启下面这个线程,因此不会升级为重量级锁
        new Thread(()->{
            synchronized (o){
                parseObjectHeader(getObjectHeader(o));
            }
        }).start();
    }

输出:

Class Pointer: 11111000 00000001 10100001 01011001 
Mark Word:
	ThreadID(54bit): 00000000 00000000 00000000 00000000 00000000 00000000 000000
	epoch: 00
	age (4bit): 0000
	biasedLockFlag (1bit): 1
	LockFlag (2bit): 01

Class Pointer: 11111000 00000001 10100001 01011001 
Mark Word:
	ThreadID(54bit): 00000000 00000000 00000000 00000000 00000010 10101110 100110
	epoch: 00
	age (4bit): 0000
	biasedLockFlag (1bit): 1
	LockFlag (2bit): 01


Class Pointer: 11111000 00000001 10100001 01011001 
Mark Word:
	javaThread*(62bit,include zero padding): 00000000 00000000 00000000 00000000 00100010 00011011 11110110 111000
	LockFlag (2bit): 00

可以看出,由偏向锁101升级为了轻量级锁00

下面来测试重量级锁,要测试重量级锁,必须要产生锁竞争:

    @Test
    public void testHeavyLock(){
        //这里没有禁止偏向锁延迟,所以最初是无锁状态
        Obj o=new Obj();
        parseObjectHeader(getObjectHeader(o));
        synchronized (o){
            parseObjectHeader(getObjectHeader(o));
        }
        //重量级锁
        for(int i=0;i<2;++i)//线程数大于1时(交错执行),会升级成重量级锁
            new Thread(()->{
                synchronized (o){
                    parseObjectHeader(getObjectHeader(o));
                }
            }).start();
    }

输出:

Class Pointer: 11111000 00000001 10110001 01011001 
Mark Word:
	hashcode (31bit): 0000000 00000000 00000000 00000000 
	age (4bit): 0000
	biasedLockFlag (1bit): 0
	LockFlag (2bit): 01

Class Pointer: 11111000 00000001 10110001 01011001 
Mark Word:
	javaThread*(62bit,include zero padding): 00000000 00000000 00000000 00000000 00000011 00000111 11100010 100010
	LockFlag (2bit): 00


Class Pointer: 11111000 00000001 10110001 01011001 
Mark Word:
	javaThread*(62bit,include zero padding): 00000000 00000000 00000000 00000000 00011101 11110111 00111010 100010
	LockFlag (2bit): 10


Class Pointer: 11111000 00000001 10110001 01011001 
Mark Word:
	javaThread*(62bit,include zero padding): 00000000 00000000 00000000 00000000 00011101 11110111 00111010 100010
	LockFlag (2bit): 10

可以看出这里是从无锁(001)到轻量级锁(00)到重量级锁(10),这里没有出现偏向锁的原因是没有禁止偏向锁延迟,所以没有测试到偏向锁,作为对比,开启禁止选项后,其输出为:(可以看出直接从偏向锁升级到重量级锁)

Class Pointer: 11111000 00000001 10110001 01011001 
Mark Word:
	ThreadID(54bit): 00000000 00000000 00000000 00000000 00000000 00000000 000000
	epoch: 00
	age (4bit): 0000
	biasedLockFlag (1bit): 1
	LockFlag (2bit): 01

Class Pointer: 11111000 00000001 10110001 01011001 
Mark Word:
	ThreadID(54bit): 00000000 00000000 00000000 00000000 00000011 00111001 100110
	epoch: 00
	age (4bit): 0000
	biasedLockFlag (1bit): 1
	LockFlag (2bit): 01


Class Pointer: 11111000 00000001 10110001 01011001 
Mark Word:
	javaThread*(62bit,include zero padding): 00000000 00000000 00000000 00000000 00011110 00101010 01011010 010010
	LockFlag (2bit): 10

Class Pointer: 11111000 00000001 10110001 01011001 
Mark Word:
	javaThread*(62bit,include zero padding): 00000000 00000000 00000000 00000000 00011110 00101010 01011010 010010
	LockFlag (2bit): 10

这里额外提醒一下,如果使用了对象的hashcode方法,那么将禁止偏向锁,原因就是hashcode需要占用对象头的31bit空间,结果就导致没有空间存储偏向锁标记了,如下图所示:

 

对于synchronized的几种使用方式,见我的文章:https://blog.csdn.net/lovemylife1234/article/details/102733422

更多Java多线程相关代码见我的github: https://github.com/xycodec/Java-MultiThread-Study

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值