6 декабря 2011 г.

Java puzzle System.exit and locks.

Встретил сегодня у Peter Lawrey в его блоге Vanilla Java статью с головоломкой. Известно, что при вызове System.exit() секции finally не выполняются. А что будет с блокировками?
private static final Object lock = new Object();

public static void main( String... args ) {
    Runtime.getRuntime().addShutdownHook( new Thread( new Runnable() {
        @Override
        public void run() {
            System.out.println( "Locking" );
            synchronized ( lock ) {
                System.out.println( "Locked" );
            }
        }
    }));
    synchronized ( lock ) {
        System.exit(0);
    }
}
Что напечатает этот код?

Собственно, уже по тому, как это подано -- можно догадаться об ответе :) Да, код напишет Locking, после чего зависнет -- JVM не будет завершаться.

Лишнее напоминание о том, что это только на уровне языка java конструкция synchronized( lock ){..} выглядит чем-то неразделимым. На уровне байт-кода инструкции monitorenter/monitorexit -- вполне себе отдельные инструкции, и вовсе не обязаны идти парами в рамках одного метода. И блок synchronized на уровне байт-кода выглядит примерно как
monitorenter lock
try{
   ...
}finally{
    monitorexit lock
}
И то, что System.exit() может эту парность разорвать -- выглядит здесь уже довольно очевидно

UPD: Как мне правильно указали в комментариях, парность/непарность здесь ни при чем. Согласно спецификации Runtime.exit() (она более подробная, чем у System.exit) выполнение кода приостанавливается в точке вызова exit(), и выполняется shutdown sequence. Первая фаза shutdown sequence -- выполнение, каждый в своем, отдельном потоке, shutdown hooks. Поскольку основной поток остановлен в точке вызова exit() -- монитор захвачен. Поскольку shutdown hook выполняется в отдельном потоке -- он подвисает на попытке захватить монитор. И, в полном соответствии со спекой, подвисший shutdown hook подвешивает и весь процесс завершения JVM.

2 комментария:

  1. А можно подробней про разрыв парности? Я её тут не вижу.

    Я тут вижу, что полнстью согласно документации System.exit выполняет shutdown hooks. В данном случае это делается из synchronized блока. То есть монитор занят. То есть, другой поток, выполняющий shutdown hook, не может войти в тот же монитор и подвисает. А пока shutdown hook не закончится (согласно документации) выход по System.exit не завершается.

    ОтветитьУдалить
  2. Я, если честно, вовсе не уверен, что здесь именно про разрыв парности. Просто мне этот пазл напомнил недавно где-то (не у тебя?) читанное, что парность monitorenter/exit -- это свойство джавы как языка, а не байткода. Т.е. можно сгенерировать валидный байткод, где вход в монитор -- в одном методе, а выход -- в другом.

    Скорее всего ты прав -- здесь про другое.

    ОтветитьУдалить