Почему байт-коды Java для вызова методов неявно получают и выпускают мониторы? - PullRequest
15 голосов
/ 27 февраля 2011

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

Ответы [ 5 ]

4 голосов
/ 27 февраля 2011

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

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

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

Только мое предположение.

3 голосов
/ 27 февраля 2011

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

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

}
1 голос
/ 27 февраля 2011

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

}

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

Предотвращение ненужных блокировок могло в большей степени зависеть от производительности в более ранние дни JVM, когда скорость была редкостью.

</speculation>

0 голосов
/ 11 апреля 2012

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

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

0 голосов
/ 27 февраля 2011

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

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

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

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

...