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

时间:2011-02-26 22:42:13

标签: java jvm synchronized

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

5 个答案:

答案 0 :(得分:4)

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

所以你要调用这些同步方法很多。即使Vector有他们!您可以在没有额外字节码的情况下进行处理。

但不仅仅是代码运行时。类文件更大。额外的说明,但也设置了try / finally表格,并验证了一些顽皮的内容没有被删除。

只是我的猜测。

答案 1 :(得分:3)

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

当一个方法市场同步时,也有多余的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

}

答案 2 :(得分:1)

<speculation>

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

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的早期阶段,当速度是一种罕见的商品时,避免不必要的锁定可能会更多地受性能驱动。

</speculation>

答案 3 :(得分:0)

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

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

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

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

答案 4 :(得分:0)

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

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