开发者

Why do the Java bytecodes for invoking methods implicitly acquire and release monitors?

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 expli开发者_如何学Pythoncit 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?


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.


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

}


<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>


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.


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/

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜