— Привет, Амиго!
Хочу основательно разобрать с тобой тему wait-notify. Методы wait-notify обеспечивают удобный механизм взаимодействия нитей. Также их можно использовать для построения сложных высокоуровневых механизмов взаимодействия нитей.
Начну с небольшого примера. Пусть у нас есть программа для сервера, которая должна выполнять различные задания, которые пользователи добавляют через сайт. Пользователи добавляют различные задания в разное время. Задачи ресурсоемкие, но сервер у нас с восьмиядерным процессором — справится. Как исполнять задачи на сервере?
Во-первых, мы создадим группу нитей-исполнителей, столько же, сколько и количество ядер процессора. Каждая нить сможет работать на своем ядре: нити не будут друг другу мешать, а ядра процессора не будут простаивать.
Во-вторых, создадим объект-очередь, в который будут помещаться полученные от пользователей задания. Разным типам заданий будут соответствовать различные объекты, но все они будут реализовать интерфейс Runnable, чтобы их можно было выполнить.
— А можно пример такого объекта-задания?
— Вот смотри:
class Factorial implements Runnable
{
public int n = 0;
public long result = 1;
public Factorial (int n)
{
this.n = n;
}
public void run()
{
for (int i=2;i<=n;i++)
result*=i;
}
}
— Пока ясно.
— Отлично. Тогда разберем, как должен выглядеть объект-очередь. Что ты можешь сказать про него?
— Он должен быть thread-safe. В него кладутся объекты-задания (таски) нитью, которая принимает их от пользователей, а забираются задания нитями-исполнителями.
— Ага. А если задания временно закончились?
— Тогда нити-исполнители должны ждать, пока они появятся.
— Верно. Тогда представь, что все это можно встроить в одну очередь. Вот смотри:
public class JobQueue
{
ArrayList jobs = new ArrayList();
public synchronized void put(Runnable job)
{
jobs.add(job);
this.notifyAll();
}
public synchronized Runnable getJob()
{
while (jobs.size()==0)
this.wait();
return jobs.remove(0);
}
}
У нас есть метод getJob, который смотрит, если список работы (jobs) пуст, то нить засыпает (wait), пока в списке что-то не появится.
А есть еще метод put, который позволяет добавить в список jobs новое задание (job). Как только новое задание добавлено, вызывается метод notifyAll. Вызов этого метода пробудит все нити-исполнители, которые заснули внутри метода getJob.
— А можешь напомнить еще раз, как работают методы wait и notify?
— Метод wait вызывается только внутри блока synchronized, у объекта-мютекса. В нашем случае – это this. При этом происходит две вещи:
1) Нить засыпает.
2) Нить временно освобождает мютекс (пока не проснется).
После этого другие нити могут входить в блок synchronized и занимать этот же мютекс.
Метод notifyAll тоже можно вызвать только внутри блока synchronized у объекта-мютекса. В нашем случае – это this. При этом происходит две вещи:
1) Просыпаются все нити, которые заснули на этом же объекте-мютексе.
2) Как только текущая нить выйдет из блока synchronized, одна из проснувшихся нитей захватит мютекс и продолжит свою работу. Когда она освободит мютекс, другая проснувшаяся нить захватит мютекс и т.д.
Очень похоже на автобус. Вы заходите внутрь, хотите передать за проезд, а водителя нет. И вы «засыпаете». Со временем вас набивается целый автобус, но за проезд пока никто не передает – некому. Затем заходит водитель, вы слышите « – Передаем за проезд». И тут начинается…
— Интересное сравнение. А что такое автобус?
— А это Хулио рассказывал. Были такие странные штуки в 21 веке.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ