jvm之字节码解读——番外篇之经典面试题:从字节码角度分析synchronized的执行过程

面试过java岗位的同学,应该会很多次遇到过这个问题:synchronized这个关键字,它的原理在字节码是怎么体现的?今天我们来分析一下

首先,我们要明确,synchronized这个关键字用在方法上有2种使用方式:一是直接作为方法的修饰符,二是作为方法里synchronized块出现

synchronized直接作为方法修饰符

我们先看第一种情况——直接作为方法的修饰符

    public synchronized void test() {

    }

看看生成的字节码对应的助记符是怎样的

public synchronized void test();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 8: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lcom/bill/MyByteCodeTest2;

看到这里,我们就会发现,synchronized修饰的方法其实跟普通方法没太大区别,只是在flags多了个ACC_SYNCHRONIZED,这样jvm在执行这个方法的时候,就会知道,这是个synchronized方法

synchronized作为方法里的一个方块出现

我们直接上代码

    public void test2(Object o) {
        synchronized (o) {
        }
    }

再看看生成的字节码对应的助记符是怎样的

  public void test2(java.lang.Object);
    descriptor: (Ljava/lang/Object;)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=2
         0: aload_1
         1: dup
         2: astore_2
         3: monitorenter
         4: aload_2
         5: monitorexit
         6: goto          14
         9: astore_3
        10: aload_2
        11: monitorexit
        12: aload_3
        13: athrow
        14: return
      Exception table:
         from    to  target type
             4     6     9   any
             9    12     9   any
      LineNumberTable:
        line 5: 0
        line 6: 4
        line 7: 14
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      15     0  this   Lcom/bill/MyByteCodeTest2;
            0      15     1     o   Ljava/lang/Object;
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 9
          locals = [ class com/bill/MyByteCodeTest2, class java/lang/Object, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4

大家把注意力放在Code里面的指令里,在这些指令里,我们重点关心2个指令——monitorenter和monitorexit

monitorenter

这个指令的作用是获取到操作数栈顶的引用,并且去获取该引用对应对象的Monitor锁。如果获取成功,则继续往下执行;如果获取失败,则线程阻塞,直到获取到这个锁。

这里简单讲一下对象的Monitor锁,java里每一个对象的对象头里都有一个monitor监视器,监视器里有几个属性——指向持有ObjectMonitor对象的线程(_owner)、锁的重入次数(_recursions)、用来记录该线程获取锁的次数(_count)。线程获取锁的步骤

(1)判断_owner是否为null,如果是,则获取锁成功,如果不是,转(2)

(2)判断_owner对应的线程对象是不是当前线程,如果是,也获取锁成功,视为重入,重入次数_recursions加1;如果不是,转(3)

(3)获取锁失败,该线程获取锁的次数(_count)加1

ps:操作数栈和局部变量表的概念前文有提及,如果不懂的话这里给个传送门 jvm之字节码解读——番外篇之经典面试题:e=(a+b-c)*d的执行过程

monitorexit

这个指令比较简单,作用是释放当前对象的锁

 

我们来看看获取锁对应的字节码指令解释

         0: aload_1		// 将对象o的引用从局部变量表入操作数栈(局部变量表的_0=this,局部变量表的_1=对象0)
         1: dup		// 将栈顶的对象o的引用复制并再入栈
         2: astore_2	// 将对象o的引用(新复制来的)pop出来加载入局部变量表,意味这是新元素(栈中的新o ——>成为 局部变量表的_2)
         3: monitorenter	// 锁住当前栈顶元素(即老对象的引用o,实际上对应的是同一个对象)的monitor
         4: aload_2		// 将局部变量表中的新o入栈(局部变量表的_2=新h)
         5: monitorexit
         6: goto          14
         9: astore_3
        10: aload_2
        11: monitorexit
        12: aload_3
        13: athrow
        14: return

整个执行过程就是如同上面代码的解释一样执行了

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值