Философия Java3
Шрифт:
java.io.InterruptedlOException Receiver read exception *///:-
Классы Sender и Receiver представляют задачи, которые должны взаимодействовать друг с другом. В классе Sender создается канал PipedWriter, существующий как автономный объект, однако при создании канала PipedReader в классе Receiver конструктору необходимо передать ссылку на PipedWriter. Sender записывает данные в канал Writer и бездействует в течение случайно выбранного промежутка времени. Класс Receiver не содержит вызовов sleep или wait, но при проведении чтения методом read он автоматически блокируется
Заметьте, что потоки sender и receiver запускаются из main после того, как объекты были полностью сконструированы. Если запускать не полностью сконструированные объекты, каналы на различных платформах могут демонстрировать несогласованное поведение.
Взаимная блокировка
Итак, потоки способны перейти в блокированное состояние, а объекты могут обладать синхронизированными методами, которые запрещают использование объекта до тех пор, пока не будет снята блокировка. Возможна ситуация, в которой один поток ожидает другой поток, тот, в свою очередь, ждет освобождения еще одного потока и т. д., пока эта цепочка не замыкается на поток, который ожидает освобождения первого потока. Получается замкнутый круг потоков, которые дожидаются освобождения друг друга, и никто не может двинуться первым. Такая ситуация называется взаимной блокировкой (deadlock) (или «клинчем». — Примеч. ред.).
Если вы запускаете программу и в ней незамедлительно возникает взаимная блокировка, проблему удается немедленно отследить. По-настоящему неприятна ситуация, когда ваша программа по всем признакам работает прекрасно, но тем не менее в какой-то момент входит во взаимную блокировку. Такая опасность незаметно присутствует в программе, пока нежданно-негаданно не проявится у заказчика (и, скорее всего, легко воспроизвести эту ситуацию вам не удастся). Таким образом, тщательное проектирование программы с целью предотвращения взаимных блокировок — важнейшая часть разработки параллельных программ.
Классический пример взаимной блокировки, предложенный Эдгаром Дейк-строй — задача об обедающих философах. В стандартной формулировке говорится о пяти философах, но, как будет показано далее, допустимо любое количество. Часть времени философы проводят размышляя, часть времени проводят за едой. Когда они размышляют, то не нуждаются в общих ресурсах, но во время обеда они сидят за круглым столом с ограниченным количеством столовых приборов. В описании оригинальной задачи философы пользуются вилками, и, чтобы набрать спагетти из миски в центре стола, им требуются две вилки. Наверное, задача будет выглядеть более логично, если заменить вилки палочками для еды — очевидно, что каждому философу понадобятся две палочки.
Философы, как это часто бывает, очень бедны, и они смогли позволить себе приобрести лишь пять палочек (или в более общем виде — количество палочек совпадает с количеством философов). Последние разложены кругом по столу, между философами. Когда философу захочется поесть, ему придется взять палочку слева и справа. Если с
//: concurrency/Chopstick.java
// Палочки для обедающих философов.
public class Chopstick {
private boolean taken = false;
public synchronized
void takeO throws InterruptedException { while(taken)
waitO; taken = true;
}
public synchronized void dropO { taken = false; notifyAllО;
}
Два философа (Philosopher) ни при каких условиях не смогут успешно взять (takeQ) одну и ту же палочку (Chopstick) одновременно. Если один философ уже взял палочку, другому философу придется подождать (wait), пока она не будет освобождена текущим пользователем (drop).
Когда задача Philosopher вызывает take, она ожидает, пока флаг taken не перейдет в состояние false (то есть пока палочка не будет освобождена тем философом, который держит ее в данный момент). Далее задача устанавливает флаг taken равным true, показывая тем самым, что палочка занята. Завершив работу с Chopstick, Philosopher вызывает drop, чтобы изменить флаг и оповестить (notifyAll) всех остальных философов, ожидающих освобождения палочки:
// concurrency/Philosopher java // Обедающий философ import java.util concurrent *. import java util *,
import static net mindview util Print *;
public class Philosopher implements Runnable { private Chopstick left, private Chopstick right, private final int id, private final int ponderFactor; private Random rand = new Random(47). private void pauseO throws InterruptedException { if(ponderFactor == 0) return. TimeUnit MILLISECONDS sleep(
rand.nextInt(ponderFactor * 250));
}
public Philosopher(Chopstick left. Chopstick right, int ident, int ponder) { this.left = left; this right = right; id = ident,
ponderFactor = ponder;
}
public void run { try {
while(IThread interruptedO) {
print(this + " " + "думает"), pauseO,
// Философ проголодался
print(this + " " + "берет правую").
right takeO.
print(this + " " + "берет левую"); left. takeO.
print(this + " " + "ест"); pauseO; right dropO; left.dropO.
}
} catchdnterruptedException e) {
print(this + " " + "выход через прерывание"),
public String toStringO { return "Философ " + id; } } /// ~
В методе Philosopher.run все философы непрерывно переходят от размышлений к еде, и наоборот. Метод pause делает паузу случайной продолжительности, если значение ponderFactor отлично от нуля. Итак, Philosopher думает в течение случайного промежутка времени, затем пытается захватить левую и правую палочки вызовами take, ест в течение случайного промежутка времени, а затем все повторяется.
В следующей версии программы возникает взаимная блокировка:
// concurrency/DeadlockingDiningPhi1osophers.java // Демонстрация скрытой возможности взаимной блокировки II {Args 0 5 timeout} import java util concurrent *.