Методы Object для многопоточности
Дорогу осилит идущийКогда мы знакомились с методами класса Object, мы лишь мельком упомянули о методах wait(), notify() и notifyAll(). На тот момент этого было достаточно.
В сегодняшнем уроке мы разберем их подробнее.
Предлагаю начать с изучения внешних источников:
https://metanit.com/java/tutorial/8.5.php – мне не очень нравятся объяснения, но описываемый пример достаточно удачный.
https://www.baeldung.com/java-wait-notify – просто хорошая вводная статья. Единственный минус – она на английском. Благо, с переводчиком она понятна, хоть и с некоторыми огрехами.
На что стоит обратить особое внимание:
· Методы wait(), notify() и notifyAll() позволяют гибче работать в парадигме синхронизации потоков. Обращение к этим методам за пределами synchronized-блоков (или методов) приведут к вызову исключения. Вызов методов не на объекте монитора – тоже.
· Взаимодействие через wait-notify имеет смысл, если вызывать эти методы при выполнении определенных логических условий. Так, вызов метода wait() вне блока if в большинстве случаев будет неправильным решением. Но поток может пробудиться самостоятельно, без вызова notify() другим потоком. Поэтому if лучше заменить на цикл while с тем же условием – непредвиденное пробуждение потока приведет лишь к новому вызову wait() и не сломает логику приложения.
· Метод notify() оповещает случайный поток, ожидающий освобождения монитора. Этот метод подходит лишь для ситуаций, когда ожидающий поток гарантированно один (так часто бывает в учебных примерах). Также может показаться, что использовать notify() стоит, если вам не важно, какой из потоков проснется. Это, с небольшими ограничениями, можно продемонстрировать на примере логики producer-consumer:
Допустим, у нас есть один (это важно) поток-потребитель, получающий и обрабатывающий данные. Так, поток должен обработать один пакет данных, прежде чем получить следующий (чем-то напоминает пример на baeldung). При этом потоков-поставщиков данных может быть несколько. При условии, что потребителю не важно, в каком порядке потоки поставляет данные – использование notify() выглядит удачным решением.
Подвох в том, что метод notify() вообще никак не регламентирует, какой поток будет оповещен. Т.е. возможна ситуация, когда пробуждаться будет другой поставщик, а не потребитель. В т.ч. один и тот же (не подходящий) поток несколько раз даже при повторных оповещениях. Вся надежда остается на то, что потоки могут пробудиться самостоятельно и рано или поздно пробудится потребитель. А пробуждение поставщика (самостоятельное или после notify() другого поставщика) не критично при правильно описанных условиях выполнения.
· Метод notifyAll() пробуждает все потоки, ожидающие освобождения монитора. С одной стороны, этот подход более прозрачен – у нас нет неопределенности, какой из потоков пробудится. С другой же – вы не знаете, в каком порядке потоки будут занимать монитор – ведь монитор может быть занят лишь одним потоком в единицу времени. Хорошая новость заключается в том, что при правильном описании условий, этот порядок не повлияет на выполнение программы.
Использование же notify(), кроме проблемы, описанной выше, может привести и к другим казусам, включая deadlock (что это такое мы подробнее разберем в ближайших статьях, как и другие проблемы многопоточности).
Например, если развить пример из предыдущего пункта до нескольких потребителей и производителей. Подробнее см. по ссылке (наиболее популярный ответ, а не помеченный как верный).
Подводя итог, notifyAll() в большинстве ситуаций будет лучшим выбором.
· wait() и notify() – достаточно грубые инструменты. Их стоит изучить в первую очередь для того, чтобы лучше понимать особенности взаимодействия с более тонкими инструментами, работающих на схожих принципах. Например, java.util.concurrent.locks.Condition. Я не уверен, что мы его подробно разберем в рамках этого курса, поэтому данный пункт – зацепка для интересующихся.
Как видите, ничего суперсложного в описанных методах Object нет. Главное, не забудьте изученную информацию на собеседовании – вероятно, это будет единственным ее практическим применением за пределами сегодняшней практики:)
С теорией на сегодня все!

Переходим к практике:
Задача 1:
Реализуйте имитацию отправки и получения сообщений. Один поток должен принимать сообщение, введенное с клавиатуры, другой поток должен выводить это сообщение в консоль. Выполнение программы должно завершиться при вводе пользователем «Finish».
Задача 2:
Реализуйте имитацию оптовой базы с тремя поставщиками и тремя покупателями. Максимальное число хранимых товаров определите на свой вкус.
Покупатели должны выкупать случайно сгенерированное число товаров при каждом посещении. Если товаров недостаточно – при следующем посещении они должны попытаться купить на 1 единицу товара меньше. Если это число достигает нуля – должно быть сгенерировано новое число. Если база опустела (на ней не осталось товаров) – покупатели должны прекратить ее посещение до новых поставок.
Поставщики должны поставлять случайно сгенерированное число товаров при каждом посещении. Если на базе не хватает места для всех товаров поставщика – он должен поставить максимально возможное количество, остальную поставку отложить до следующего посещения. Если число товаров для поставки достигло нуля – должно быть сгенерировано новое число. Если база заполнилась на 100% - поставщики должны прекратить попытки поставок до момента, пока заполняемость базы не достигнет 25%.
Логируйте действия покупателей и поставщиков в консоли. Программа должна завершиться при вводе пользователем «Finish» с клавиатуры. Другие пользовательские вводы не предусмотрены.
Рекомендую ограничить максимальное значение у Покупателей меньшим лимитом, чем у Поставщиков. В таком случае выполнение программы будет более наглядным.
Если что-то непонятно или не получается – welcome в комменты к посту или в лс:)
Канал: https://t.me/ViamSupervadetVadens
Мой тг: https://t.me/ironicMotherfucker
Дорогу осилит идущий!