Согласование работы нескольких подпроцессов
Возможность создания многопоточных программ заложена в язык Java с самого его создания. В каждом объекте есть три метода wait о и один метод notify о, позволяющие приостановить работу подпроцесса с этим объектом, позволить другому подпроцессу поработать с объектом, а затем уведомить (notify) первый подпроцесс о возможности продолжения работы. Эти методы определены прямо в классе object и наследуются всеми классами.
С каждым объектом связано множество подпроцессов, ожидающих доступа к объекту (wait set). Вначале этот "зал ожидания" пуст.
Основной метод wait (long miiiisec) приостанавливает текущий подпроцесс this, работающий с объектом, на miiiisec миллисекунд и переводит его в "зал ожидания", в множество ожидающих подпроцессов. Обращение к этому методу допускается только в синхронизированном блоке или методе, чтобы быть уверенными в том, что с объектом работает только один подпроцесс. По истечении miiiisec или после того, как объект получит уведомление методом notify о, подпроцесс готов возобновить работу. Если аргумент miiiisec равен о, то время ожидания не определено и возобновление работы подпроцесса возможно только после того, как объект получит уведомление методом notify().
Отличие данного метода от метода sleep о в том, что метод wait о снимает блокировку с объекта. С объектом может работать один из подпроцессов из "зала ожидания", обычно тот, который ждал дольше всех, хотя это не гарантируется спецификацией JLS.
Второй метод wait () эквивалентен wait (0). Третий метод wait (long millisec, int nanosec) уточняет задержку на nanosec наносекунд, если их сумеет отсчитать операционная система.
Метод notify () выводит из "зала ожидания" только один, произвольно выбранный подпроцесс. Метод notifyAll() выводит из состояния ожидания все подпроцессы. Эти методы тоже должны выполняться в синхронизированном блоке или методе.
Как же применить все это для согласованного доступа к объекту? Как всегда, лучше всего объяснить это на примере.
Обратимся снова к схеме "поставщик-потребитель", уже использованной в
главе 15.
Один подпроцесс, поставщик, производит вычисления, другой, потребитель, ожидает результаты этих вычислений и использует их по мере поступления. Подпроцессы передают информацию через общий экземпляр st класса store.
Работа этих подпроцессов должна быть согласована. Потребитель обязан ждать, пока поставщик не занесет результат вычислений в объект st, а поставщик должен ждать, пока потребитель не возьмет этот результат.
Для простоты поставщик просто заносит в общий объект класса store целые числа, а потребитель лишь забирает их.
В листинге 17.6 класс store не обеспечивает согласования получения и выдачи информации. Результат работы показан на рис. 17.4.
Листинг 17.6.
Несогласованные подпроцессы
class Store{
private inf inform;
synchronized public int getlnform(){ return inform; }
synchronized public void setlnform(int n){ inform = n; }
}
class Producer implements Runnable{
private Store st;
private Thread go;
Producer(Store st){
this.st = st;
go = new Thread(this);
go.start();
}
public void run(){
int n = 0;
Thread th = Thread.currentThread();
while(go == th){
st.setlnform(n);
System.out.print("Put: " + n + " ");
n++;
}
}
public void stop(){ go = null;
}
}
class Consumer implements Runnable{
private Store st;
private Thread go;
Consumer(Store st){
this.st = st;
go =-new Thread(this);
go.start () ;
}
public void run(){
Thread th = Thread.currentThread();
while(go == th) System.out.println("Got: " + st.getlnformf));
}
public void stop(){ go = null; }
}
class ProdCons{
public static void main(String[] args){
Store st = new Store();
Producer p = new Producer(st);
Consumer с = new Consumer(st);
try{
Thread.sleep(30);
}catch(InterruptedException ie){}
p.stop(); c.stop();
}
}
Рис. 17.4.
Несогласованная работа двух подпроцессов
В листинге 17.7 в класс store внесено логическое поле ready, отмечающее процесс получения и выдачи информации. Когда новая порция информации получена от поставщика Producer, в поле ready заносится значение true, получатель consumer может забирать эту порцию информации. После выдачи информации переменная ready становится равной false.
Но этого мало. То, что получатель может забрать продукт, не означает, что он действительно заберет его. Поэтому в конце метода setinformf) получатель уведомляется о поступлении продукта методом notify о. Пока поле ready не примет нужное значение, подпроцесс переводится в "зал ожидания" методом wait (). Результат работы программы с обновленным классом store показан на рис. 17.5.
Листинг 17.7.
Согласование получения и выдачи информации
class Store{
private int inform = -1;
private boolean ready;
synchronized public int getlnform(){
try{
if (! ready) wait();
ready = false;
return inform;
}catch(InterruptedException ie){
}finally!
notify();
}
return -1;
}
synchronized public void setlnform(int n)(
if (ready)
try{
wait ();
}catch(InterruptedException ie){}
inform = n;
ready = true;
notify();
}
}
Поскольку уведомление поставщика в методе getinformo должно происходить уже после отправки информации оператором return inform, оно включено В блок finally{}
Рис. 17.5.
Согласованная работа подпроцессов
Обратите внимание: сообщение "Got: 0" отстает на один шаг от действительного получения информации.