为什么调用方法的Java字节码隐式获取和释放监视器?

我一直在阅读Java虚拟机指令集,并注意到当使用指令调用标记为同步的方法(例如invokestatic,invokevirtual等)时,由特定的字节码指令来获取接收器上的监视器宾语。 类似地,从方法返回时,由方法同步时指令释放监视器的指令。 这看起来很奇怪,因为有明确的monitorenter和monitorexit字节码来管理监视器。 JVM是否有特殊原因以这种方式设计这些指令,而不是仅仅编译方法以在适当的位置包含monitorenter和monitorexit指令?

早在90年代中期,就没有Java JIT编译器和微同步被认为是一个非常好的主意。

所以你经常调用这些同步方法。 甚至Vector也有他们! 您可以在没有额外字节码的情况下进行处理。

但不仅仅是代码运行时。 类文件更大。 额外的说明,但也设置了try / finally表和validation顽皮的东西没有被插入。

只是我的猜测。

你在问为什么有两种方法可以做同样的事情?

当一个方法是市场同步时,也有多余的monitorenter / exit指令。 如果它只有monitorenter / exit指令你就不会打赌从外部看到该方法是同步的(没有读取实际的代码)

有两种或更多种方法可以做同样的事情。 每个人都有相对的优势和劣势。 (例如,许多单字节指令是双字节指令的短版本)

编辑:我必须在问题中遗漏一些东西,因为调用者不需要知道被调用者是否同步

 public static void main(String... args) { print(); printSynchronized(); printSynchronizedInternally(); } public static void print() { System.out.println("not synchronized"); } public static synchronized void printSynchronized() { System.out.println("synchronized"); } public static void printSynchronizedInternally() { synchronized(Class.class) { System.out.println("synchronized internally"); } } 

产生代码

 public static void main(java.lang.String[]); Code: 0: invokestatic #2; //Method print:()V 3: invokestatic #3; //Method printSynchronized:()V 6: invokestatic #4; //Method printSynchronizedInternally:()V 9: return public static void print(); Code: 0: getstatic #5; //Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #6; //String not synchronized 5: invokevirtual #7; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return public static synchronized void printSynchronized(); Code: 0: getstatic #5; //Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #8; //String synchronized 5: invokevirtual #7; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return public static void printSynchronizedInternally(); Code: 0: ldc_w #9; //class java/lang/Class 3: dup 4: astore_0 5: monitorenter 6: getstatic #5; //Field java/lang/System.out:Ljava/io/PrintStream; 9: ldc #10; //String synchronized internally 11: invokevirtual #7; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 14: aload_0 15: monitorexit 16: goto 24 19: astore_1 20: aload_0 21: monitorexit 22: aload_1 23: athrow 24: return Exception table: from to target type 6 16 19 any 19 22 19 any } 

通过将锁管理委派给调用者 ,现在可以进行一些优化。 例如,假设您有一个这样的类:

 public class Foo { public synchronized void bar() { // ... } } 

并假设此调用者类使用它:

 public class Caller { public void call() { Foo foo = new Foo(); // implicit MONITORENTER on foo's lock foo.bar(); // implicit MONITOREXIT on foo's lock } } 

基于转义分析,JVM知道foo永远不会逃脱该线程。 因此,它可以避免隐式MONITORENTERMONITOREXIT指令。

在JVM的早期阶段,当速度是一种罕见的商品时,避免不必要的锁可能更多地受性能驱动。

您是否在问JOD是否可以通过查看方法的属性来推断它们时,为什么同步方法使用显式监视器入口和退出指令?

我猜这是因为,除了方法之外,还可以同步任意代码块:

 synchronized( some_object ){ // monitorentry some_object System.out.println("I am synchronised!"); } // monitorexit some_object 

因此,对同步方法和同步块使用相同的指令是有意义的。

正在搜索同一个问题,并发现了以下文章。 看起来方法级同步生成比块级同步稍高效的字节代码。 对于块级同步,生成显式字节代码以处理未对方法级同步进行的exception。 所以可能的答案是,这两种方法用于使方法级同步稍微快一些。

http://www.ibm.com/developerworks/ibm/library/it-haggar_bytecode/