Целью связи потока является включение потоков отправлять сигналы друг другу. С другой стороны, связь потока позволяет потокам ждать сигналов из других потоков.
Общение через общие объекты
Простой способ отправлять сигналы между потоками - установить значение сигнала в переменные общего объекта. Поток A Устанавливает логическую переменную элемента HasdatatoProcess в True в блоке синхронизации, а поток B также считывает переменную элемента HasdatatoProcess в блоке синхронизации. В этом простом примере используется объект, удерживающий сигнал и предоставляет методы установки и проверки:
открытый класс mysignal {защищенный логический hasdatatoProcess = false; Общедоступный синхронизированный логический hasdatatoprocess () {return this.hasdatatoprocess; } public void void sethasdatatoprocess (boolean hasdata) {this.hasdatatoprocess = hasdata; }}Поток A и B должны получить ссылку на общий экземпляр MySignal для связи. Если ссылки, которые они держат, указывают на разные экземпляры Mysingal, друг друга не смогут обнаружить сигналы друг друга. Данные, которые должны быть обработаны, могут храниться в области общего кеша, которая хранится отдельно от экземпляра MySignal.
Занят подождать
Поток B, который готовится к обработке данных, ожидает доступных данных. Другими словами, он ждет сигнала из потока A, который заставляет HasdatatoProcess () вернуть True. Поток B работает в цикле, чтобы ждать этого сигнала:
Защищенный mysignal sharedsignal = ...... while (! sharedsignal.hasdatatoprocess ()) {// ничего не делайте ... занят ожиданием}wate (), notify () и notifyall ()
Занятое ожидание не использует процессор, работающий с потоком ожидания, если среднее время ожидания не очень короткое. В противном случае, разумно заставить ожидающую нить спать или не работать, пока она не получит сигнал, которого он ждет.
У Java есть встроенный механизм ожидания, позволяющий нитью не работать при ожидании сигналов. Класс java.lang.object определяет три метода, wait (), notify () и notifyall (), для реализации этого механизма ожидания.
Как только поток вызывает метод wait () любого объекта, он становится неконтролируемым состоянием, пока другой поток не вызовет метод notify () того же объекта. Чтобы вызовать wait () или уведомление (), поток должен сначала получить блокировку этого объекта. То есть поток должен вызовать wait () или уведомлять () в блоке синхронизации. Ниже приведена модифицированная версия MySingal - mywaitNotify с использованием wait () и notify ():
открытый класс monitorobject {} public class mywaitnotify {monitorObject myMonitorObject = new MonitorObject (); public void dowait () {synchronized (myMonitorObject) {try {myMonitorObject.wait (); } catch (прерывание Exception e) {...}}} public void donotify () {synchronized (myMonitorObject) {myMonitorObject.notify (); }}}Поток ожидания позвонит Dowait (), в то время как поток для пробуждения позвонит Donotify (). Когда поток вызывает метод notify () объекта, один из потоков, ожидающих, чтобы объект был пробужден и разрешено выполнять (Примечание: этот поток для пробуждения, является случайным, и нельзя указать, какой поток просыпается). Также предоставляется метод notifyall () для разбуждения всех потоков, ожидающих данного объекта.
Как вы можете видеть, будь то поток ожидания или поток для пробуждения, он вызывает wait () и уведомление () в блоке синхронизации. Это обязательно! Если поток не удерживает блокировку объекта, он не может вызовать wait (), notify () или notifyall (). В противном случае будет выброшено исключение allogalMonitorStateException.
(Примечание: вот как реализован JVM. Когда вы звоните в ожидание, он сначала проверяет, является ли текущий поток владельцем блокировки, и выбрасывает allogalmonitorstateexcept.)
Но как это возможно? При ожидании потока в блоке синхронизации, разве он не всегда удерживает блокировку объекта монитора (объект MyMonitor)? Может ли резьба ожидания заблокировать поток для пробуждения, входящий в синхронный блок Donotify ()? Ответ: это правда. Как только поток вызывает метод wait (), он выпускает блокировку на объекте удерживаемого монитора. Это позволит другим потокам вызовать wait () или уведомление ().
Как только поток разбудится, вызов метода wait () не может быть выведен немедленно, пока не будет вызван notify ().
открытый класс myWaitNotify2 {monitorObject myMonitorObject = new MonitorObject (); логический wassignaled = false; public void dowait () {synchronized (myMonitorObject) {if (! wassignaled) {try {myMonitorObject.wait (); } catch (прерывание Exception e) {...}} // Очистить сигнал и продолжить работу. wassignaled = false; }} public void donotify () {synchronized (myMonitorObject) {wasSignaled = true; myMonitorObject.notify (); }}}
Поток выходит из собственного блока синхронизации. Другими словами, поток Wowken должен восстановить блокировку объекта монитора, прежде чем он сможет выйти из вызова метода wait (), поскольку вызов метода ожидания выполняется в блоке синхронизации. Если несколько потоков пробуждаются notifyall (), то в то же время только один поток может выйти из метода wait (), потому что каждый поток должен получить блокировку объекта монитора перед выходом на Wait ().
Пропущенные сигналы
Методы notify () и notifyall () не сохраняют метод, который их вызывает, потому что, когда эти два метода называются, возможно, что ни один поток не в состоянии ожидания. Сигнал уведомления был отброшен. Следовательно, если поток вызовет уведомление () перед уведомлением перед вызовом wait (), поток ожидания пропустит этот сигнал. Это может или не может быть проблемой. Тем не менее, в некоторых случаях это может заставит всегда ждать ожидания и больше не просыпаться, потому что нить пропускает сигнал пробуждения.
Чтобы избежать потери сигналов, они должны быть сохранены в классе сигнала. В примере MyWaitNotify сигнал уведомления должен храниться в переменной члена экземпляра MyWaitNotify. Вот модифицированная версия MyWaitNotify:
открытый класс myWaitNotify2 {monitorObject myMonitorObject = new MonitorObject (); логический wassignaled = false; public void dowait () {synchronized (myMonitorObject) {if (! wassignaled) {try {myMonitorObject.wait (); } catch (прерывание Exception e) {...}} // Очистить сигнал и продолжить работу. wassignaled = false; }} public void donotify () {synchronized (myMonitorObject) {wasSignaled = true; myMonitorObject.notify (); }}}Обратите внимание, что метод Donotify () устанавливает Wasignaled переменную на True, прежде чем вызов notify (). В то же время, обратите внимание, что метод Dowait () проверяет переменную WASIGNALED перед вызовом wat (). Фактически, если сигнал не получен в течение периода времени между предыдущим вызовом Dowait () и этим вызовом Dowait (), он только вызовет wat ().
(Доказательство. Чтобы избежать потери сигнала, используйте переменную, чтобы сохранить, была ли она уведомлена.
Фальшивый просыпается
По какой -то причине возможно, что поток просыпается без вызова notify () и notifyall (). Это называется ложными пробуждениями. Проснись без причины.
Если в методе MyWaitNotify2 возникает ложный пробуждение, то поток ожидания может выполнять последующие операции, даже если он не получает правильный сигнал. Это может вызвать серьезные проблемы с вашим приложением.
Чтобы предотвратить ложный пробуждение, переменные элемента, которые удерживают сигнал, будут проверены в цикле времени, а не в выражении IF. Такой цикл, который называется Spin Lock (примечание: этот подход должен быть осторожным. Текущая спина реализации JVM потребляет процессор. Если метод донотификации не требуется в течение длительного времени, метод Dowait будет вращаться непрерывно, и процессор будет потреблять слишком много). Пробужденная нить будет вращаться до тех пор, пока условие в спиновой блокировке (в петле) не станет ложным. Следующая модифицированная версия mywaitnotify2 показывает это:
открытый класс myWaitNotify3 {monitorObject myMonitorObject = new MonitorObject (); логический wassignaled = false; public void dowait () {synchronized (myMonitorObject) {while (! wassignaled) {try {myMonitorObject.wait (); } catch (прерывание Exception e) {...}} // Очистить сигнал и продолжить работу. wassignaled = false; }} public void donotify () {synchronized (myMonitorObject) {wasSignaled = true; myMonitorObject.notify (); }}}Обратите внимание, что метод wait () находится в цикле while, а не в выражении IF. Если резьба ожидания просыпается, не получая сигнал, переменная WASIGNALED станет ложной, а цикл while будет выполнена снова, что приведет к возвращению потока пробуждения вернуться в состояние ожидания.
Несколько потоков ждут одного и того же сигнала
Если у вас есть несколько потоков, ожидающих, и вы пробуждены Notifyall (), но только одному разрешено продолжить выполнение, использование цикла while также является хорошим способом. Только один поток может каждый раз получать блокировку объекта монитора, что означает, что только один поток может выйти из вызова wait () и очистить флаг Wasignaled (устанавливается на False). Как только этот поток выходит из блока синхронизации Dowait (), другие потоки выходят из вызова wait () и проверяют значение переменной WASIGNALED в цикле while. Тем не менее, этот флаг был очищен первым пробужденным потоком, поэтому остальная часть пробужденных потоков вернется в состояние ожидания до следующего случая, когда сигнал не появится.
Не вызовите wat () в строковых константах или глобальных объектах
(Доказательство примечание: Константа строки, упомянутая в этой главе, относится к переменным с постоянными значениями)
Более ранняя версия этой статьи использует String Constants («») в качестве объекта труб в примере MywaitNotify. Вот пример:
открытый класс mywaitnotify {string myMonitorObject = ""; логический wassignaled = false; public void dowait () {synchronized (myMonitorObject) {while (! wassignaled) {try {myMonitorObject.wait (); } catch (прерывание Exception e) {...}} // Очистить сигнал и продолжить работу. wassignaled = false; }} public void donotify () {synchronized (myMonitorObject) {wasSignaled = true; myMonitorObject.notify (); }}}Проблема, вызванная вызовом wait () и notify () в блоке синхронизации пустой строки в виде блокировки (или другой постоянной строки), заключается в том, что JVM/компилятор преобразует постоянную строку в тот же объект. Это означает, что даже если у вас есть 2 разных экземпляра MyWaitNotify, все они относятся к одному и тому же экземпляру пустой строки. Это также означает, что существует риск того, что поток, вызывая Dowait () в первом экземпляре MyWaitNotify, будет пробуждена потоком, вызывая Donotify () во втором экземпляре MyWaitNotify. Эта ситуация может быть сделана следующим образом:
Сначала это не может быть большой проблемой. В конце концов, если Donotify () вызывается во втором экземпляре MyWaitNotify, то на самом деле происходит то, что потоки A и B ошибочно пробуждаются. Поток пробуждения (a или b) проверит значение сигнала в цикле while, а затем вернется в состояние ожидания, потому что Donotify () не вызывает первого экземпляра MywaitNotify, и это экземпляр, которого он ждет. Эта ситуация эквивалентна созданию ложного пробуждения. Поток A или B просыпается без обновления значения сигнала. Но код обрабатывает эту ситуацию, поэтому поток возвращается в состояние ожидания. Помните, что даже если 4 потока вызовы wait () и notify () в одном и том же экземпляре общей строки, сигналы в Dowait () и Donotify () будут сохранены 2 экземплярами MywaitNotify соответственно. Вызов Donotify () на mywaitnotify1 может разбудить поток mywaitnotify2, но значение сигнала будет сохранено только в mywaitnotify1.
Проблема заключается в том, что, поскольку Donotify () только вызовы вызовов notify () вместо notifyall (), даже если в одном и том же экземпляре (пустая строка), только 4 потока, ожидают только один поток. Таким образом, если поток A или B пробуждается сигналом, отправленным C или D, он проверит его собственное значение сигнала, чтобы увидеть, получен ли какой -либо сигнал, а затем вернется в состояние ожидания. Ни C, ни D не были пробуждены, чтобы проверить значение сигнала, которое они фактически получили, поэтому сигнал был потерян. Эта ситуация эквивалентна проблеме отсутствующих сигналов, упомянутых выше. C и D были отправлены на сигнал, но ни один из них не может ответить на сигнал.
Если метод Donotify () вызовы notifyall () вместо уведомления (), все потоки ожидания будут пробуждены, а значение сигнала будет проверено по очереди. Поток A и B вернется в состояние ожидания, но только один поток в C или D замечает сигнал и выходит из вызова метода Dowait (). Другой в C или D вернется в состояние ожидания, потому что поток, который получил сигнал, очищает значение сигнала (установленное в FALSE) в процессе выхода DOWAIT ().
После прочтения приведенного выше абзаца вы можете попытаться использовать notifyall () вместо notify (), но это плохая идея, основанная на производительности. Когда только один поток может реагировать на сигнал, нет никаких оснований разбудить все потоки каждый раз.
SO: в механизме wait ()/notify () не используйте глобальные объекты, строковые константы и т. Д. Соответствующий уникальный объект следует использовать. Например, каждый экземпляр MyWaitNotify3 имеет свой собственный объект монитора вместо вызова wait ()/notify () на пустой строке.
Выше представлена информация о многопоточной связи Java и связи. Мы будем продолжать добавлять соответствующую информацию в будущем. Спасибо за поддержку этого сайта!