Главная » Java » wait, notifyAll и notify Java

1

 

Механизмы синхронизированной блокировки позволяют успешно предотвратить возможное взаимное влияние нескольких потоков, но нам нужны, кроме Того, средства обеспечения взаимодействия потоков. С этой целью применяются метод ожидания, wait, позволяющий приостановить выполнение потока до того Момента, пока не будет удовлетворено определенное условие, и методы оnовеще1tuя, notifyAll и notify, которые сообщают ожидающим потокам о том, что произошло некое событие, способное повлиять на результат про верки условия Ожидания. Методы wait, notifyAll и notify определены в составе класса Object и наследуются всеми производными классами. Они применяются по отношению к конкретным объектам – точно так же, как и блокировки.

Существует стандартный образец кода, которому важно следовать при использовании методов ожидания и оповещения. Потоку, ожидающему выполнения некоторого условия, всегда надлежит выполнять действия, подобные таким:

synchronized void doWhenCondition() {

whilе (! условие)

wait() ;

/ / … Выполнить то, что необходимо, если условие равно true ‘"

}

Здесь необходимо сделать ряд замечаний.

·      Все функции по обеспечению взаимодействия потоков должны выполняться в рамках синхронизированного кода. Если это требование не удовлетворяется, состояние объекта не может считаться стабильным. Например, если метод не объявлен как synchronized, после выполнения блока whilе нельзя твердо гарантировать, что проверяемое условие остается равным true, поскольку другой поток может изменить ситуацию.

·      Один из важных аспектов контракта метода wait состоит в том, что при приостановке выполнения потока он атомарным образом освобождает блокировку объекта. Говоря об атомарной связи между приостановкой выполнения потока и освобождением блокировки, мы имеем в виду, что эти действия неразделимы и выполняются только совместно. В противном случае существовала бы опасность возникновения условия состязания: событие оповещения могло бы произойти после освобождения блокировки, но перед приостановкой выполнения потока (оповещение не повлияло бы на поток и уведомление оказалось бы попросту утерянным). Когда поток после получения "разрешающего" уведомления возобновляет свою работу, блокировка атомарным образом восстанавливается.

·      Условие ожидания должно всегда проверяться циклически. Не думайте, что достаточно про верить его только один раз, – после того как условие удовлетворено, оно может измениться вновь. Другими словами, нельзя заменять whilе на if.

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

synchronized void changecondition() {

// … изменить некоторое значение,

// используемое в выражении условия ожидания …

notifyALL(); // или notify();

}

Метод notifyALL оповещает все ожидающие потоки, а notify выбирает для этого только один поток.

Потоки могут ожидать выполнения условий (возможно, различных), Относящихся к одному и тому же объекту. Если условия действительно различны, для оповещения всех ожидающих потоков следует всегда использовать метод notifyAll вместо notify. В противном случае может случиться так, что уве-

домление попадет к какому-нибудь потоку, который, на самом деле, ожидает выполнения другого условия. Этот поток обнаружит, что его условие все еще не разрешено положительно, и вновь перейдет в состояние ожидания, а поток, действительно заинтересованный в информации, которую выдает notify, возможно, так никогда ее и не получит. Метод notify позволяет несколько повысить эффективность кода и может при меняться только в тех случаях, когда:

·      все потоки ожидают выполнения одного и того же условия;

·      самое большее один поток приобретет преимущества ввиду выполнения условия;

• подобному контракту подчиняются все возможные производные классы.

ВО всех остальных ситуациях надлежит использовать notifyALL. Если производный класс не удовлетворяет любому из двух условий, указанных в начале списка, работоспособность кода базового класса, в котором применяется notify, может быть нарушена. В этом смысле важно, чтобы информация о принятых классом стратегиях ожидания и оповещения, в том числе и о применяемых объектных ссылках (this или других), была документирована с целью Возможного использования при проектировании расширенных классов.

Следующий пример иллюстрирует реализацию класса Queue (очередь), ссылка на объект которого присутствовала в коде класса PrintServer, рассмотренного в разделе 10.2 на странице 247. В составе класса объявлены методы, позволяющие помещать элементы в очередь и удалять их.

            class Сеll {       // Элемент очереди

            Сеll next;          // Ссылка на следующий элемент очереди

            Object item;      // Содержимое текущего элемента

cell(Object item) {

this.item = item;

            }

}

class Queue {

private Cell head, tail; // Элементы в "голове" и "хвосте" очереди

public synchronized void add(Object о) {

Cell р = new Cell(0); // представить объект о

// в виде элемента очереди

if (tail == null) head = р;

else

tail.next = р;

p.next = null;

tail = р;

notifyALL();                              // Оповестить все ожидающие потоки о том,

// что в очередь добавлен элемент

}

public synchronized Object take() throws InterruptedException

{

while (head == null)                              // Ждать уведомления о добавлении

 wait() ;                                    // элемента

           

            Cell p = head;                          // Запомнить элемент, занимающий место

 // в "голове" очереди

            head = head.next;

            if (head == null)

                        tail = null;

            return p.item;

            }

}

Приведенная здесь реализация очереди схожа с теми, которые применяются в однопоточном программировании, – такими как, скажем, singleLinkQueue (см. раздел 3.5 на странице 102). Отличия касаются нескольких аспектов: методы обозначены модификатором synchronized, предотвращающим опасность взаимного влияния потоков; при добавлении элемента в очередь ожидающие потоки оповещаются; если очередь становится пустой, метод take, вместо того чтобы возвратить null, ожидает, пока какой-либо другой поток добавит в очередь элемент, и поэтому выполнение take блокируется до момента появления в очереди доступного элемента. Многие потоки (не только один) способны добавлять элементы в очередь и извлекать их из очереди. Поскольку метод wait Может генерировать исключение типа interruptedException, последний необходимо упомянуть в предложении throws объявления метода take (подробные сведения об исключении InterruptedException приведены ниже).

Если сейчас вновь возвратиться к примеру класса PrintServer, вы поймете, что хотя, как кажется, основная работа внутреннего потока сосредоточена в бесконечном цикле и сводится к непрерывным попыткам извлечения из очереди очередного задания на печать, обращение к методу take (и далее к wait) означает, что поток приостанавливает выполнение, если в очереди отсутствуют элементы. Если бы мы, напротив, использовали версию класса очереди, в котором метод take в случае, если очередь пуста, возвращал бы null, потоку пришлось бы обращаться к take непрерывно и расходовать при этом ресурс процессора такую ситуацию принято обозначать термином активное ожидание (busywaiting). В многопоточных системах подобный подход используется крайне редко. Поток должен всегда приостанавливать выполнение до тех пор, пока условие, необходимое для продолжения его работы, не будет удовлетворено. Именно в этом состоит суть процессов взаимодействия потоков посредством механизмов wait и notifyALL/notify.

 

Источник: Арнолд, Кен, Гослинг, Джеймс, Холмс, Дэвид. Язык программирования Java. 3-е изд .. : Пер. с англ. – М. : Издательский дом «Вильяме», 2001. – 624 с. : ил. – Парал. тит. англ.

По теме:

  • Комментарии

1 комментарий

  1. эдди says:

    хорошо сказано, сразу в самом начале понял что и как