Программирование на языке Java

         

Взаимодействие подпроцессов



Взаимодействие подпроцессов

В Java имеется элегантный механизм общения между подпроцессами, основанный на методах wait, notify и notifyAll. Эти методы реализованы, как final-методы класса Object, так что они имеются в любом Java-классе. Все эти методы должны вызываться только из синхронизованных методов. Правила использования этих методов очень просты:

  • wait - приводит к тому, что текущий подпроцесс отдает управление и переходит в режим ожидания - до тех пор пока другой под-процесс не вызовет метод notify с тем же объектом.
  • notify - выводит из состояния ожидания первый из подпроцессов, вызвавших wait с данным объектом.
  • notifyAll - выводит из состояния ожидания все подпроцессы, вызвавшие wait с данным объектом.

Ниже приведен пример программы с наивной реализацией проблемы поставщик-потребитель. Эта программа состоит из четырех простых классов: класса Q, представляющего собой нашу реализацию очереди, доступ к которой мы пытаемся синхронизовать; поставщика (класс Producer), выполняющегося в отдельном подпроцессе и помещающего данные в очередь; потребителя (класс Consumer), тоже представляющего собой подпроцесс и извлекающего данные из очереди; и, наконец, крохотного класса PC, который создает по одному объекту каждого из перечисленных классов. class Q { int n; synchronized int get() { System.out.println("Got: " + n); return n; } synchronized void put(int n) { this.n = n; System.out. println("Put: " + n); } } class Producer implements Runnable { Q q; Producer(Q q) { this.q = q; new Thread(this, "Producer").start(); } public void run() { int i = 0; while (true) { q.put(i++); } } } class Consumer implements Runnable { Q q; Consumer(Q q) { this.q = q; new Thread(this, "Consumer").start(); } public void run() { while (true) { q.get(); } } } class PC { public static void main(String args[]) { Q q = new Q(); new Producer(q); new Consumer(q); } }

Хотя методы put и get класса Q синхронизованы, в нашем примере нет ничего, что бы могло помешать поставщику переписывать данные по того, как их получит потребитель, и наоборот, потребителю ничего не мешает многократно считывать одни и те же данные. Так что вывод программы содержит вовсе не ту последовательность сообщений, которую нам бы хотелось иметь: С:\> java PC Put: 1 Got: 1 Got: 1 Got: 1 Got: 1 Got: 1 Put: 2 Put: 3 Put: 4 Put: 5 Put: 6 Put: 7 Got: 7

Как видите, после того, как поставщик помещает в переменную n значение 1, потребитель начинает работать и извлекает это значение 5 раз подряд. Положение можно исправить, если поставщик будет при занесении нового значения устанавливать флаг, например, заносить в логическую переменную значение true, после чего будет в цикле проверять ее значение до тех пор пока поставщик не обработает данные и не сбросит флаг в false.

Правильным путем для получения того же результата в Java является использование вызовов wait и notify для передачи сигналов в обоих направлениях. Внутри метода get мы ждем (вызов wait), пока Producer не известит нас (notify), что для нас готова очередная порция данных. После того, как мы обработаем эти данные в методе get, мы извещаем объект класса Producer (снова вызов notify) о том, что он может передавать следующую порцию данных. Соответственно, внутри метода put, мы ждем (wait), пока Consumer не обработает данные, затем мы передаем новые данные и извещаем (notify) об этом объект-потребитель. Ниже приведен переписанный указанным образом класс Q. class Q { int n; boolean valueSet = false; synchronized int get() { if (!valueSet) try wait(); catch(InterruptedException e): System.out.println("Got: " + n); valueSet = false; notify(); return n; } synchronized void put(int n) { if (valueSet) try wait(); catch(InterruptedException e); this.n = n; valueSet = true; System.out.println("Put: " + n); notify(); } }

А вот и результат работы этой программы, ясно показывающий, что синхронизация достигнута. С:\> java Pcsynch Put: 1 Got: 1 Put: 2 Got: 2 Put: 3 Got: 3 Put: 4 Got: 4 Put: 5 Got: 5



Содержание раздела