Подтвердить что ты не робот

Что может вызвать IllegalMonitorStateException изнутри синхронизированного блока?

Сегодня мы сталкиваемся с чрезвычайно неожиданным исключением. Внутри синхронизированного блока мы вызываем wait(), и он бросает IllegalMonitorStateException. Что может вызвать это?

Это происходит в хорошо проверенном исходном коде: http://svn.apache.org/viewvc/river/jtsk/trunk/src/com/sun/jini/jeri/internal/mux/Mux.java?view=markup#l222

Мы устранили очевидные причины:

  • мы синхронизированы по правой переменной? Да, это muxLock
  • Это изменчивая переменная? Нет, muxLock является окончательным.
  • Мы используем любые странные "-XX:" флаги JVM, которые могут повлиять на поведение монитора? Нет, но мы запускаем JVM, встроенный в приложение С++ через JNI.
  • Это странная JVM? Нет, это Sun 1.6.0_25 win/x64 JRE
  • Это известная ошибка JVM? Невозможно найти что-либо релевантное в http://bugs.sun.com/bugdatabase

Итак, я пытаюсь думать о более надуманных объяснениях.

  • может произойти ошибка неконтролируемой памяти, вызывающая зависание состояния монитора? Мы смотрим на это, но мы еще не видим признаков ошибок памяти.

ОБНОВЛЕНИЕ: (на основе комментария)

Я также проверил из stacktrace и breakpoint, что поток действительно находится внутри синхронизированного блока, когда генерируется исключение. Это не тот случай, когда какой-либо другой несвязанный код испускает исключение (если только что-то НЕ ДЕЙСТВИТЕЛЬНО запутывает Eclipse!)

4b9b3361

Ответ 1

Единственная подозрительная вещь, которую я вижу, что вы передаете ссылку на 'this' на какой-либо другой объект в своем конструкторе. Возможно ли это (на самом деле, что маловероятно), что благодаря странному переупорядочению вещей, если какой-то другой поток получает эту ссылку на 'this' и вызывает метод, использующий muxlock, все может пойти крайне неправильно.

Спецификация Java Language довольно специфична:

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

Другими словами, если другой поток получает ссылку 'this' перед тем, как конструктор будет завершен, окончательное поле 'muxlock', возможно, еще не было правильно инициализировано. В общем, публикация ссылки на 'this' до завершения конструктора может быть довольно опасной, особенно в поточных ситуациях.

Некоторая потенциально полезная дискуссия о таких вещах: http://madpropellerhead.com/random/20100328-java-final-fields-are-not-as-final-as-you-may-think

Для некоторого более старого, но все же полезного общего обсуждения того, почему публикация 'this' в конструкторе является очень плохой идеей в целом, см., например: http://www.ibm.com/developerworks/java/library/j-jtp0618/index.html

Ответ 2

http://svn.apache.org/viewvc/river/jtsk/trunk/src/com/sun/jini/jeri/internal/mux/Mux.java?r1=1069292&r2=1135026&diff_format=h

здесь я вижу, что таймаут был добавлен в последнее время

убедитесь, что startTimeout > 0, иначе вы будете ждать (0) или wait (-n), это, вероятно, вызовет IllegalMonitorStateException

EDIT: Ok выше - это катастрофа. Но давайте попробуем это:

мы находимся в конструкторе Mux: http://svn.apache.org/viewvc/river/jtsk/trunk/src/com/sun/jini/jeri/internal/mux/Mux.java?view=markup

line 176 мы создаем SocketChannelConnectionIO и затем передаем это после этого, мы разрываем и выполняем разные потоки.

в конструкторе SocketChannelConnectionIO, определенном здесь: http://svn.apache.org/viewvc/river/jtsk/trunk/src/com/sun/jini/jeri/internal/mux/SocketChannelConnectionIO.java?view=markup line 112, мы регистрируем канал с новым обработчиком().

обработчик что-то делает в chanel и функции let say функция handleReadReady выполняется, мы синхронизируем на muxLock.

теперь мы все еще находимся в конструкторе, поэтому объект в финале все еще изменчив!!! пусть предположим, что это изменилось, теперь у нас есть что-то ожидающее на разных muxLock

Один из миллиона сценариев

ИЗМЕНИТЬ

http://svn.apache.org/viewvc/river/jtsk/trunk/src/com/sun/jini/jeri/internal/mux/Mux.java?revision=1135026&view=co

Mux(SocketChannel channel,
    int role, int initialInboundRation, int maxFragmentSize)
    throws IOException
    {
    this.role = role;
    if ((initialInboundRation & ~0x00FFFF00) != 0) {
        throw new IllegalArgumentException(
        "illegal initial inbound ration: " +
        toHexString(initialInboundRation));
    }
    this.initialInboundRation = initialInboundRation;
    this.maxFragmentSize = maxFragmentSize;

    //LINE BELOW IS CAUSING PROBLEM it passes this to SocketChannelConnectionIO
    this.connectionIO = new SocketChannelConnectionIO(this, channel);

    //Lets assume it stops here we are still in constructor
    //and we are not in synchronized block

    directBuffersUseful = true;
    }

теперь в конструкторе SocketChannelConnectionIO http://svn.apache.org/viewvc/river/jtsk/trunk/src/com/sun/jini/jeri/internal/mux/SocketChannelConnectionIO.java?revision=1069292&view=co

SocketChannelConnectionIO(Mux mux, SocketChannel channel)
    throws IOException
{
    super(mux);
    channel.configureBlocking(false);
    this.channel = channel;
    //Line below we are registering to the channel with mux that is still mutable
    //this is the line that actually is causing the problem move that to 
    // start() and it should work 
    key = selectionManager.register(channel, new Handler());
}

переместите этот код в start(), должен работать key = selectionManager.register(channel, new Handler()); (я предполагаю, что start is executet, когда мы хотим начать поиск)

/**
 * Starts processing connection data.
 */
void start() throws IOException {
    key = selectionManager.register(channel, new Handler());
    key.renewInterestMask(SelectionKey.OP_READ);
}

Но было бы гораздо лучше не создавать SocketChannelConnectionIO в конструкторе мультиплексора, но, возможно, где-то после этого то же самое для второго конструктора, создающего StreamConnectionIO с этим

Ответ 3

Ответ на мой взгляд, что это либо ошибка, либо кто-то изменил объект за ссылкой, несмотря на то, что он был окончательным. Если вы можете воспроизвести его, я рекомендую установить точку останова для чтения/записи в поле muxlock, чтобы узнать, косвенно ли она затронута. Вы можете проверить идентификационный код muxlock в первой строке синхронизированного блока и перед ожиданием и уведомлением с соответствующими записями журнала или точками останова. С отражением вы можете изменить окончательные ссылки. Цитата из http://download.oracle.com/javase/6/docs/api/java/lang/reflect/Field.html:

"Если базовое поле является окончательным, метод выдает исключение IllegalAccessException , если setAccessible (true) не удалось выполнить это поле, и это поле не статично. Установка конечного поля таким образом имеет смысл только во время десериализации или восстановления экземпляров классов с пустым финальным полем, прежде чем они станут доступными для доступа другими частями программы. Использование в любом другом контексте может иметь непредсказуемые последствия, в том числе случаи, когда другие части программы продолжают использовать исходное значение этого поля."

Возможно, это ошибка в eclispe, и во время отладки это как-то меняет поле. Является ли он воспроизводимым вне эклипса? Поместите printstractrace в catch и посмотрите, что произойдет.

Ответ 4

Членские переменные не так окончательны, как хотелось бы. Сначала нужно поместить синхронизированный объект в конечную локальную переменную. Это не объясняет, почему переменная-член изменяется, но если она исправляет проблему, вы, по крайней мере, знаете, что переменная-член действительно модифицирована.