Получить доступ

synchronized, мониторы и wait/notify

Взаимное исключение и видимость через монитор, reentrancy, guarded blocks с wait/notify и причины deadlock.

Что даёт synchronized

synchronized решает сразу две задачи: взаимное исключение (в критической секции по данному монитору одновременно лишь один поток) и видимость (выход из монитора happens-before следующего входа, поэтому изменения видны другому потоку).

Монитор есть у каждого объекта. synchronized-метод берёт монитор this (или класса для static), а synchronized(obj)-блок — монитор obj. Видимость без взаимного исключения не работает: оба свойства даёт именно правильная синхронизация.

Reentrancy

Встроенные мониторы реентерабельны: поток, уже держащий монитор, может войти в другой synchronized-блок по тому же монитору без самоблокировки (счётчик удержаний увеличивается). Это позволяет synchronized-методу вызывать другой synchronized-метод того же объекта.

Без реентерабельности любой такой вложенный вызов был бы deadlock-ом самого с собой.

Guarded blocks: wait/notify

wait/notify/notifyAll координируют потоки по условию и вызываются только при удерживаемом мониторе. wait() отпускает монитор и засыпает, пока другой поток не вызовет notify по тому же монитору.

Два железных правила: проверять условие в цикле while (а не if) из-за ложных пробуждений (spurious wakeups) и общих notify, и предпочитать notifyAll, когда на мониторе ждут разные условия, иначе можно разбудить «не тот» поток и потерять сигнал.

synchronized (lock) {
    while (!ready) {        // while, не if — защита от spurious wakeup
        lock.wait();
    }
    consume();
}

Что выбирать монитором

Нельзя синхронизироваться по общим/кэшируемым объектам: строковые литералы (интернируются), Integer в диапазоне кэша, Boolean — их разделяет весь JVM, и чужой код случайно заблокирует ту же ссылку. Синхронизация по this в публичном классе тоже рискованна: внешний код может взять ваш монитор.

Хорошая практика — приватный финальный объект-замок: private final Object lock = new Object();.

Deadlock, livelock, starvation

Deadlock — циклическое ожидание: поток A держит замок 1 и ждёт 2, поток B держит 2 и ждёт 1. Livelock — потоки активно реагируют друг на друга и не двигаются вперёд. Starvation — поток вечно не получает ресурс из-за приоритетов/нечестности.

Профилактика deadlock: единый глобальный порядок захвата замков, короткие критические секции, никакого внешнего IO/вызовов чужого кода под замком, и tryLock с таймаутом (явные Lock) вместо бесконечного ожидания.

Как отвечать на собеседовании

Скажите, что synchronized даёт и взаимное исключение, и видимость (happens-before через монитор), объясните реентерабельность, разберите wait/notify с правилом while-цикла и notifyAll, и назовите причины deadlock с профилактикой (единый порядок захвата). Бонус — почему нельзя лочиться по String/Integer/this.

Вопросы по теме

Читайте также

Закрытая бета

Знать тему — половина дела

На живом собеседовании sotto слышит вопрос интервьюера и показывает короткий ответ и план. Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем