Почему методы wait() и notify() объявлены в классе Object в Java?
Почему методы wait()
и notify()
объявлены в классе Object
, а не в классе Thread
?
5 ответ(ов)
Причина заключается в том, что вы ожидаете на конкретном объекте (или, более точно, его мониторе) для использования данной функциональности.
Я думаю, вы могли бы ошибаться в понимании того, как работают эти методы. Они не просто работают на уровне потоков, то есть это не случай простого вызова wait()
, после которого вы будете пробуждены следующим вызовом notify()
. Вместо этого вы всегда вызываете wait()
на конкретном объекте и только вызовы notify
на этом объекте пробуждают вас.
Это удобно, потому что, в противном случае, примитивы параллелизма не могли бы масштабироваться. Это было бы эквивалентно наличию глобальных пространств имен, поскольку любые вызовы notify()
в любой части вашей программы могли бы нарушить работу любого конкурентного кода, так как они пробуждали бы любые потоки, блокирующиеся на вызове wait()
. Вот почему вы вызываете их на конкретном объекте; это задает контекст для работы пары wait-notify, так что когда вы вызываете myBlockingObject.notify()
на приватном объекте, вы можете быть уверены, что только потоки, которые вызывали методы ожидания в вашем классе, будут пробуждены. Какой-то поток Spring, который может ожидать на другом объекте, не будет пробужден этим вызовом и наоборот.
Правка: Или, чтобы рассмотреть это с другой стороны – я предполагаю, что из вашего вопроса вы думали, что сможете получить доступ к ожидающему потоку и вызвать notify()
на этом Потоке, чтобы его пробудить. Причина, по которой это не делается, заключается в том, что вам придется самостоятельно заниматься множеством дополнительных задач. Поток, который собирается ожидать, должен будет опубликовать ссылку на себя где-то, чтобы другие потоки могли ее видеть; это должно быть правильно синхронизировано для обеспечения согласованности и видимости. А когда вы хотите разбудить поток, вам нужно будет получить доступ к этой ссылке, разбудить ее и удалить оттуда, откуда вы ее получили. Это потребует гораздо больше ручной работы и увеличивает вероятность ошибок (особенно в среде с параллельными потоками) по сравнению с простыми вызовами myObj.wait()
в спящем потоке и myObj.notify()
в потоке-разбудителе.
Наиболее простая и очевидная причина заключается в том, что любая сущность (не только поток) может быть монитором для другого потока. Методы wait
и notify
вызываются на мониторе. Рабочий поток взаимодействует с монитором. Поэтому методы wait
и notify
находятся в классе Object
, а не в классе Thread
.
Механизм синхронизации включает в себя концепцию — монитор объекта. Когда вызывается метод wait(), запрашивается монитор, и дальнейшее выполнение приостанавливается до тех пор, пока монитор не будет захвачен или не произойдет исключение InterruptedException. Когда вызывается метод notify(), монитор освобождается.
Рассмотрим сценарий, в котором методы wait() и notify() находились бы в классе Thread вместо класса Object. Предположим, в какой-то момент кода вызывается currentThread.wait()
, после чего происходит доступ к объекту anObject
.
//.........
currentThread.wait();
anObject.setValue(1);
//.........
Когда вызывается currentThread.wait()
, запрашивается монитор currentThread
, и дальнейшее выполнение останавливается до тех пор, пока монитор не будет захвачен или не произойдет InterruptedException. Теперь, находясь в состоянии ожидания, если из другого потока будет вызван метод foo()
другого объекта anotherObject
, находящегося в currentThread
, выполнение будет заблокировано, даже если вызываемый метод foo()
не обращается к anObject
. Если бы первый метод wait() был вызван на anObject
, а не на самом потоке, то другие вызовы методов (не обращающиеся к anObject
) на объектах, находящихся в том же потоке, не заблокировались бы.
Таким образом, вызов методов wait() и notify() на классе Object (или его подклассах) обеспечивает более высокую степень конкуренции, и именно поэтому эти методы находятся в классе Object, а не в классе Thread.
Некоторые другие ответы используют слово "монитор", но никто не объясняет, что оно означает.
Термин "монитор" был введён ещё в 1970-х годах и обозначал объект, который имел собственную внутреннюю блокировку и связанный с ней механизм ожидания/уведомления. Подробности можно найти здесь.
Двадцать лет спустя был краткий момент, когда настольные многопроцессорные компьютеры только появились на рынке, и было модно считать, что правильным подходом к проектированию программного обеспечения для них будет создание объектно-ориентированных программ, в которых каждый объект является монитором.
Оказалось, что это не было столь полезной идеей, но именно в этот краткий момент и была изобретена языковая среда Java.
Проще говоря:
Для того чтобы вызвать методы wait()
или notify()
, необходимо владеть монитором объекта. Это означает, что вызовы wait()
или notify()
должны находиться внутри синхронизированного блока.
synchronized(monitorObj) {
monitorObj.wait(); // или даже notify()
}
Вот почему эти методы являются частью класса Object
.
Разница между "wait()" и "sleep()" в Java
Что значит 'synchronized'?
Разница между интерфейсами Runnable и Callable в Java
Следует ли использовать отдельные экземпляры ScriptEngine и CompiledScript для каждого потока?
SwingUtilities.invokeLater: Вызов кода в потоке событий Swing