Можно ли упростить синхронизированный блок до блока 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 или мне все еще нужны эти проверки?


person Clashsoft    schedule 23.01.2016    source источник
comment
Да, это правильно. Фактически, это точный синтаксис для java.util.concurrent.locks.Lock.   -  person Boris the Spider    schedule 23.01.2016
comment
Я не уверен, что понял вашу мысль. Если вы тот, кто реализует компилятор, замена synchronized на try … finally ничего не упрощает, так как тогда вы все равно должны реализовать try … finally, не так ли? Так что вам все равно придется позаботиться о любом операторе return самостоятельно.   -  person Holger    schedule 25.01.2016
comment
@Holger конечно, но мне пришлось бы реализовать одно и то же дважды, так что есть в два раза больше способов совершить ошибку. В настоящее время синхронизированная генерация байт-кода — это просто специализация оператора try/finally.   -  person Clashsoft    schedule 25.01.2016


Ответы (2)


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

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

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

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

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
person AdamSkywalker    schedule 23.01.2016

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

person Antimony    schedule 23.01.2016
comment
Да, возможно, бесконечное зацикливание на исключении — отличная функция, хотя я не уверен, действительно ли это обязательно, просто потому, что это появляется в одном примере кода спецификации… - person Holger; 25.01.2016