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