Иллюстрированный самоучитель по Java
Согласование
работы нескольких подпроцессов
Возможность создания многопоточных
программ заложена в язык 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" отстает на один шаг от действительного получения информации.