Можно ли упростить синхронизированный блок до блока Try-Finally на уровне байт-кода?


Когда я пишу свой собственный компилятор для Java-подобного языка, у меня возникают проблемы с компиляцией synchronized blocks. Я пришел к следующей идее, чтобы упростить их до блоков try-finally:

synchonized (obj) {
     statements...
}

Можно заменить на

Object _lock = obj
_monitorEnter(lock)
try {
    statements...
}
finally {
    _monitorExit(lock)
}

Где _monitorEnter и _monitorExit представляют инструкции MONITORENTER и MONITOREXIT.

Прав ли я с этим предположением о том, как synchronized компилируется, или я что-то упускаю?

править

Моя реализация ранее имела некоторые специальные обработка для операторов return и throw в теле. В принципе, он будет вручную загружать все переменные lock и MONITOREXIT перед каждой инструкцией *RETURN или THROW. Это обрабатывается блоком finally, или мне все еще нужны эти проверки?

2 4

2 ответа:

Ваши предположения верны. Блок synchronized в языке Java реализуется с помощью инструкций monitorenter и monitorexit. Вы можете просмотреть детали спецификации JVM здесь.

Синхронизация в виртуальной машине Java осуществляется монитором вход и выход, либо явно (с помощью монитора и инструкции monitorexit) или неявно (с помощью вызова метода и инструкции по возврату).

Компилятор генерирует байт-код, который будет обрабатывать все исключения, брошенные внутри тела synchronized, поэтому ваш подход try-finally будет работать нормально.

Спецификация Инструкции finally ничего не говорит о выпуске мониторов. Пример, приведенный в первой ссылке, показывает байт-код для простого метода, обернутого в блок synchronized. Как вы можете видеть, любое возможное исключение обрабатывается для обеспечения выполнения команды monitorexit. Вы должны реализовать такое же поведение в своем компиляторе (написать код, который выпустит монитор внутри наконец заявление).

void onlyMe(Foo f) {
    synchronized(f) {
        doSomething();
    }
}

Method void onlyMe(Foo)
0   aload_1             // Push f
1   dup                 // Duplicate it on the stack
2   astore_2            // Store duplicate in local variable 2
3   monitorenter        // Enter the monitor associated with f
4   aload_0             // Holding the monitor, pass this and...
5   invokevirtual #5    // ...call Example.doSomething()V
8   aload_2             // Push local variable 2 (f)
9   monitorexit         // Exit the monitor associated with f
10  goto 18             // Complete the method normally
13  astore_3            // In case of any throw, end up here
14  aload_2             // Push local variable 2 (f)
15  monitorexit         // Be sure to exit the monitor!
16  aload_3             // Push thrown value...
17  athrow              // ...and rethrow value to the invoker
18  return              // Return in the normal case
Exception table:
From    To      Target      Type
4       10      13          any
13      16      13          any

Компилятор Java компилирует синхронизированные блоки во что-то вроде try-finally, как вы уже догадались. Однако есть одно незначительное отличие-обработка исключений ловит исключения, вызванные monitorexit, и бесконечно петляет, пытаясь освободить блокировку. Нет никакого способа, чтобы указать поток управления достаточно, как это в Java.