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

发布于 2024-10-19 09:31:34 字数 392 浏览 5 评论 0原文

I've been reading up on the Java Virtual Machine Instruction Set and noticed that when using instructions to invoke methods (e.g. invokestatic, invokevirtual, etc.) that are marked synchronized, it's up to that particular bytecode instruction to acquire the monitor on the receiver object. Similarly, when returning from a method, it's up to the instruction that leaves the method to release the monitor when the method is synchronized. This seems strange, given that there are explicit monitorenter and monitorexit bytecodes for managing monitors. Is there a particular reason for the JVM designing these instructions this way, rather than just compiling the methods to include the monitorenter and monitorexit instructions where appropriate?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

葬心 2024-10-26 09:31:34

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

因此,您经常调用这些同步方法。甚至 Vector 也有它们!您无需解释额外的字节码即可处理。

但不仅仅是在代码运行时。类文件更大。额外的说明,还设置了 try/finally 表并验证是否有一些顽皮的东西没有被插入。

只是我的猜测。

Back in the mid-90s, there were no Java JIT compilers and micro-synchronisation was thought to be a really great idea.

So you are calling these synchronised method a lot. Even Vector has 'em! You could deal without the extra bytecodes to interpret.

But not just when the code is being run. The class file is bigger. Extra instructions, but also setting up the try/finally tables and verification that something naughty hasn't been slipped in.

Just my guess.

青巷忧颜 2024-10-26 09:31:34

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

当一个方法与市场同步时,再有监视器进入/退出指令将是多余的。如果它只有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

}

Are you asking why are there two ways of doing the same thing?

When a method is market as synchronized, it would be redundant to also have monitorenter/exit instructions. If it only had monitorenter/exit instruction you would not bet able to see externally that the method is synchronized (without reading the actual code)

There are more than a few examples of two or more ways of doing the same thing. Each has relative strengths and weaknesses. (e.g. many of the single byte instructions are short versions of a two byte instruction)

EDIT: I must be missing something in the question because the caller doesn't need to know if the callee is synchronized

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");
    }
}

produces the code

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

}
朮生 2024-10-26 09:31:34

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

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>

By delegating lock management onto the caller, some optimizations are now possible. For example, suppose you have a class like this:

public class Foo {

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

}

And suppose it is used by this caller class:

public class Caller {

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

}

Based on escape analysis, the JVM knows that foo never escapes the thread. Because of this, it can avoid the implicit MONITORENTER and MONITOREXIT instructions.

Avoiding unnecessary locks may have been more performance-driven in earlier days of the JVM when speed was a rare commodity.

</speculation>

强者自强 2024-10-26 09:31:34

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

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

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

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

Are you asking why synchronised methods use explicit monitor entry and exit instruction when the JVM could infer them by looking at the method's attributes?

I would guess it is because, as well as methods it is possible to synchronise arbitrary blocks of code:

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

Therefore it makes sense to use the same instructions for both synchronised methods and synchronised blocks.

盗心人 2024-10-26 09:31:34

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

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

Was searching on the same question, and came across the following article. Looks like method level synchronization generates slightly more efficient byte code than block level synchronization. For block level synchronization, explicit byte code is generated to handle exceptions which is not done for method level synchronization. So a possible answer could be, these two ways are used to make method level synchronization slightly faster.

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文