Ожидание/уведомление Java в повторно входящих синхронизированных блоках

Мое понимание блоков Java synchronized() заключается в том, что если поток уже владеет блокировкой объекта, он может войти в другой блок, синхронизированный с тем же объектом (синхронизация с повторным входом). Ниже я считаю, что JVM использует счетчик ссылок для увеличения/уменьшения количества раз, когда поток получал блокировку, и что блокировка снимается только тогда, когда счетчик равен нулю.

Итак, мой вопрос: если кто-то столкнется с фрагментом кода, который выглядит так:

synchronized(this)
{
    if (condition == WAITING)
    {
        synchronized(this)
        {
            condition = COMPLETE;

            notify();

            try
            {
                wait();
            }
            catch(InterruptedException  e)
            {
            }
        }
    }
    else
        condition = READY;
}

что конкретно происходит при вызове wait()? Он просто уменьшает счетчик или освобождает блокировку независимо от счетчика?

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

Во втором случае я вообще не вижу смысла во втором синхронизированном блоке.

В документации для wait() говорится

«Текущий поток должен владеть монитором этого объекта. Поток освобождает владение этим монитором и ждет, пока другой поток не уведомит потоки, ожидающие на мониторе этого объекта, чтобы проснуться либо с помощью вызова метода уведомления, либо метода notifyAll. Затем поток ждет, пока он может повторно получить право собственности на монитор и возобновить выполнение»,

поэтому я думаю, что второй случай правильный, но я могу ошибаться. Так я что-то упустил, или я просто наткнулся на лишний синхронизированный блок, который можно так же легко удалить из кода?


person user2725525    schedule 16.04.2015    source источник
comment
Если вы видите подобный код (некоторые из которых не компилируются), я предлагаю вам удалить и переписать его. Примечание: при первом вызове уведомления ничего не произойдет, так как ни один поток не будет ожидать. Во второй раз вы не будете вызывать уведомление, поскольку условие изменилось, поэтому ваш первый поток будет ждать вечно.   -  person Peter Lawrey    schedule 16.04.2015
comment
Я (плохо) отредактировал код, чтобы упростить его в качестве примера. Материал условия и некомпилируемые ошибки были мной, а не самим кодом/проблемой. Кроме того, условие обновляется в другом месте другим потоком. Основной код кода предназначен для потока, ожидающего завершения другого потока, чтобы получить уведомление об изменении условия. Затем он вызывает уведомление в другом месте, когда оно закончилось.   -  person user2725525    schedule 16.04.2015
comment
Почему бы вам не переписать это, чтобы использовать фьючерсы, чтобы вы знали, когда поток завершится?   -  person M.K.    schedule 16.04.2015
comment
а) потому что я переношу это на C++, не поддерживая текущий код Java, и б) даже в этом случае слово «полный» было неправильным с моей стороны. ожидание и уведомление здесь действительно зависят от внутреннего состояния, а не от какого-либо фактического вычислительного вывода.   -  person user2725525    schedule 16.04.2015
comment
Фьючерсы нужны не только для вычислений. Также у С++ есть будущее и обещание.   -  person M.K.    schedule 17.04.2015


Ответы (1)


Нет ничего, что требовало бы повторного получения блокировки после if.

wait() также полностью снимет блокировку (в противном случае это может привести к взаимоблокировке).

Единственная причина для второго synchronized, которую я вижу, заключается в том, что он ранее использовал другой объект, и кто-то по ошибке изменил его, чтобы использовать тот же this.

person Kayaman    schedule 16.04.2015
comment
Это было и мое предположение. Я переношу довольно большую базу кода Java на C++ и пытаюсь реорганизовать ее, чтобы вообще не использовать std::recursive_mutex. Спасибо за информацию! - person user2725525; 16.04.2015
comment
Просто для подтверждения этого сценария: когда поток сталкивается с внешним синхронизированным блоком, он получает блокировку объекта. Затем он сталкивается со вторым синхронизированным блоком, который запускает повторный вход. Notify вызывается, но ни один поток не может получить блокировку, поскольку текущий поток все еще имеет заблокированный объект. Наконец, вызывается ожидание, которое заставляет текущий поток отказаться от удерживаемой им блокировки. Вы согласны? - person M.K.; 16.04.2015
comment
Точно. Это также похоже на конструкцию, с которой можно было бы гораздо лучше обращаться с классом из java.util.concurrent, но я полагаю, что код устарел, и автор, возможно, не был знаком с ними. - person Kayaman; 16.04.2015
comment
Разве ваше утверждение: Нет ничего, что гарантировало бы повторное получение блокировки после if., не вводит в заблуждение? Повторный вход приведет к увеличению счетчика блокировок. Другими словами, второй синхронизированный блок будет гарантировать повторное получение блокировки. - person M.K.; 16.04.2015
comment
Фиксированная формулировка. Английский не является моим родным языком, поэтому, возможно, мне не следует использовать причудливые языковые конструкции :) Увеличение счетчика блокировки имеет смысл только в рекурсивных ситуациях, так что возврат из рекурсивного синхронизированного контекста не приводит к слишком раннему отказу от блокировки. Однако здесь ничего подобного нет (поскольку wait() все равно, сколько раз он синхронизируется на одном и том же объекте). - person Kayaman; 17.04.2015
comment
@Kayaman, это все еще неправильно. Когда текущий поток встречает второй синхронизированный блок, он делает текущий поток реентерабельным (как вы согласились с моим предыдущим анализом). Таким образом, после if есть что-то, что потребует повторного получения блокировки. - person M.K.; 17.04.2015
comment
Я не уверен, что понимаю тебя. Блокировки являются реентерабельными, а не потоками. Второй синхронизированный блог не вызывает повторный вход. Реентерабельность является неявным свойством. Затем notify() пробуждает другой поток и начинает wait()ing, позволяя другому потоку работать. Нет никакой разницы между этим кодом и удалением внутренней синхронизации. - person Kayaman; 17.04.2015
comment
Давайте продолжим это обсуждение в чате. - person M.K.; 17.04.2015