Брюс Эккель - Философия Java3
//: concurrency/waxomatic/WaxOMatic.java // Простейшее взаимодействие задач, package concurrency.waxomatic, import java.util concurrent.*; import static net.mindview util.Print.*;
class Car {
private boolean waxOn = false; public synchronized void waxedО {
waxOn = true; // Готово к обработке notifyAll О;
}
public synchronized void buffedО {
waxOn = false; // Готово к нанесению очередного слоя notifyAll О;
}
public synchronized void waitForWaxingO throws InterruptedException { while(waxOn == false) waitO;
}
public synchronized void waitForBuffingO throws InterruptedException {
while(waxOn == true) продолжение &
waitO;
}
}
class WaxOn implements Runnable { private Car car;
public WaxOn(Car c) { car = c; } public void run() { try {
while( IThread interruptedO) { printnbC'Wax On! "); Ti mellni t. MILLISECONDS. si eep( 200); car.waxedO; car.waitForBuffingO;
}
} catchdnterruptedException e) {
printC'Exiting via interrupt");
}
print("Ending Wax On task");
}
}
class WaxOff implements Runnable { private Car car;
public WaxOff(Car c) { car = c; } public void runO { try {
while(IThread.interruptedO) { car.waitForWaxingO; printnbC'Wax Off! "); Ti mellni t. MILLI SECONDS. s 1 eep (200); car.buffedO;
}
} catchdnterruptedException e) {
printC'Exiting via interrupt");
}
printC'Ending Wax Off task");
public class WaxOMatic {
public static void main(String[] args) throws Exception { Car car = new CarO;
ExecutorService exec = Executors.newCachedThreadPoolО;
exec.execute(new WaxOff(car));
exec.execute(new WaxOn(car));
TimeUnit SECONDS.sieep(5); // Небольшая задержка. .
exec.shutdownNowO; // Прерывание всех задач
}
} /* Output:
Wax On! Wax Off! Wax On! Wax Off! Wax On! Wax Off! Wax On! Wax Off! Wax On! Wax Off! Wax On! Wax Off! Wax On! Wax Off! Wax On! Wax Off! Wax On! Wax Off! Wax On! Wax Off! Wax On! Wax Off! Wax On! Wax Off! Wax On! Exiting via interrupt Ending Wax On task Exiting via interrupt Ending Wax Off task *///:-
Класс Саг содержит одну логическую переменную waxOn, которая описывает текущее состояние процесса полировки.
Метод waitForWaxing() проверяет флаг waxOn, и, если он равен false, вызывающая задача приостанавливается вызовом wait(). Очень важно, что это происходит в синхронизированном методе. При вызове wait() поток приостанавливается, а блокировка снимается. Последнее принципиально, потому что для безопасного изменения состояния объекта (например, для присваивания waxOn значения true, без чего приостановленная задача не сможет продолжить работу) блокировка должна быть доступна для другой задачи. В нашем примере при вызове другой задачей метода waxed(), указывающего, что пришло время что-то сделать, для задания истинного значения waxOn необходимо установить блокировку. Затем waxed() вызывает notifyAll(); задача, приостановленная вызовом wait(), активизируется. Для этого нужно сначала заново получить блокировку, освобожденную при входе в wait(). Задача не активизируется, пока блокировка не станет доступной.
Использование каналов для ввода/вывода между потоками
Часто бывает полезно организовать взаимодействие потоков посредством механизмов ввода/вывода. Библиотеки потоков могут предоставлять поддержку ввода/вывода между потоками в форме каналов (pipes). Последние представлены в стандартной библиотеке ввода/вывода Java классами PipedWriter (позволяет потоку записывать в канал) и PipedReader (предоставляет возможность другому потоку считывать из того же канала).
Простой пример взаимодействия двух потоков через канал:
//• concurrency/PipedlO.java
// Использование каналов для ввода/вывода между потоками
import java.util.concurrent.*;
import java.io.*;
import java.util.*;
import static net.mindview.util.Print.*;
class Sender implements Runnable {
private Random rand = new Random(47); private PipedWriter out = new PipedWriterO; pubTic PipedWriter getPipedWriterO { return out, } public void run() { try {
while(true)
for(char с = 'А'; с <= 'z'; С++) { out write(c);
TimeUnit.MILLISECONDS.sleep(rand.nextlnt(500));
}
} catch(IOException e) {
print(e + " Sender write exception"); } catch(InterruptedException e) {
print(e + " Sender sleep interrupted");
}
}
class Receiver implements Runnable { private PipedReader in;
public Receiver(Sender sender) throws IOException {
in = new PipedReader(sender.getPipedWriterO).
}
public void run() { try {
while(true) {
// Блокируется до появления следующего символа, printnb("Read- " + (char)in.readO + ");
}
} catch(IOException e) {
print(e + " Receiver read exception");
public class PipedIO {
public static void main(String[] args) throws Exception { Sender sender = new SenderO; Receiver receiver = new Receiver(sender); ExecutorService exec = Executors.newCachedThreadPool0; exec.execute(sender); exec.execute(receiver); TimeUnit.SECONDS.sleep(4); exec.shutdownNowO;
}
} /* Output:
Read: A, Read: B. Read: C. Read: D, Read- E, Read. F, Read: G, Read: H, Read: I. Read: J. Read: K, Read: L, Read: M, java.lang.InterruptedException: sleep interrupted Sender sleep interrupted
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; } } /// ~