Подтвердить что ты не робот

Почему Java-байт-коды для вызова методов неявно приобретают и выпускают мониторы?

Я читал в набор инструкций виртуальной машины Java и заметил, что при использовании инструкций для вызова методов (например, invokestatic, invokevirtual и т.д.), которые помечены как синхронизированные, вплоть до этой конкретной инструкции байт-кода для получения монитора на объекте получателя. Аналогичным образом, при возврате из метода это до инструкции, которая оставляет метод освобождения монитора при синхронизации метода. Это кажется странным, учитывая, что для управления мониторами существуют явные контрольные и monitorexit байт-коды. Есть ли конкретная причина для JVM, проектирующего эти инструкции таким образом, а не просто компиляцию методов для включения инструкций monitorenter и monitorexit, где это необходимо?

4b9b3361

Ответ 1

В середине 90-х годов не было компиляторов Java JIT, и микросинхронизация считалась действительно отличной идеей.

Итак, вы часто вызываете этот синхронный метод. Даже Vector имеет их! Вы можете обходиться без дополнительных байткодов для интерпретации.

Но не только при запуске кода. Файл класса больше. Дополнительные инструкции, но также установление таблиц try/finally и проверка того, что что-то непослушное не было добавлено.

Просто думаю.

Ответ 2

Вы спрашиваете, почему есть два способа сделать одно и то же?

Когда метод является рыночным как синхронизированный, было бы излишним также иметь команды 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

}

Ответ 3

<speculation>

Делегируя управление блокировкой на вызывающего, теперь возможны некоторые оптимизации. Например, предположим, что у вас есть такой класс:

public class Foo {

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

}

И пусть он используется этим классом вызывающего:

public class Caller {

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

}

На основе анализа эвакуации JVM знает, что foo никогда не выходит из потока. Из-за этого он может избежать неявных инструкций MONITORENTER и MONITOREXIT.

Избегание ненужных блокировок, возможно, было более эффективным с точки зрения производительности в ранние дни JVM, когда скорость была редким товаром.

</speculation>

Ответ 4

Вы спрашиваете, почему синхронизированные методы используют явные команды ввода и выхода монитора, когда JVM может их вывести, просмотрев атрибуты метода?

Я бы предположил, что это потому, что, как и методы, можно синхронизировать произвольные блоки кода:

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

Поэтому имеет смысл использовать те же инструкции как для синхронизированных методов, так и для синхронизированных блоков.

Ответ 5

Выполнял поиск по тому же вопросу и наткнулся на следующую статью. Похоже, синхронизация уровня метода генерирует несколько более эффективный байтовый код, чем синхронизация уровня блока. Для синхронизации уровня блока генерируется явный байт-код для обработки исключений, которые не выполняются для синхронизации уровня метода. Таким образом, возможный ответ может быть, эти два способа используются для ускорения синхронизации уровня метода.

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