Философы, как это часто бывает, очень бедны, и они смогли позволить себе приобрести лишь пять палочек (или в более общем виде — количество палочек совпадает с количеством философов). Последние разложены кругом по столу, между философами. Когда философу захочется поесть, ему придется взять палочку слева и справа. Если с какой-либо стороны желаемая палочка уже в руке другого философа, только что оторвавшемуся от размышлений придется подождать ее освобождения:
//: 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 *.
public class DeadlockingDiningPhi1osophers {
public static void main(String[] args) throws Exception { int ponder = 5, if(args length > 0)
ponder = Integer.parselnt(args[0]); int size = 5; if(args length > 1)
size = Integer parselnt(args[l]); ExecutorService exec = Executors newCachedThreadPoolО; Chopstick[] sticks = new Chopstick[size]; for(int i = 0; i < size; i++)
sticksCi] = new ChopstickO; for(int 1=0. i < size, i++)
exec execute(new PhiTosopherC
sticks[i], sticks[(i+l) % size], i, ponder)); if(args length == 3 && args[2].equals("timeout")) TimeUnit.SECONDS sleep(5);
\ else {
System.out.рппШСНажмите 'Enter', чтобы завершить работу"); System in readO;
}
exec shutdownNow();
}
} ///:-
Если философы почти не тратят время на размышления, они будут постоянно конкурировать за палочки при попытках поесть, и взаимные блокировки возникают гораздо чаще.
Первый аргумент командной строки изменяет значение ponder, влияющее на продолжительность размышлений. Если философов очень много или они проводят большую часть времени в размышлениях, взаимная блокировка может и не возникнуть, хотя ее теоретическая вероятность отлична от нуля. С нулевым аргументом взаимная блокировка наступает намного быстрее.
Объектам Chopstick не нужны внутренние идентификаторы; они идентифицируются по своей позиции в массиве sticks. Каждому конструктору Philosopher передаются ссылки на правую и левую палочки Chopstick. Последнему Philosopher в качестве правой палочки передается нулевой объект Chopstick; круг замыкается. Теперь может возникнуть ситуация, когда все философы одновременно попытаются есть, и каждый из них будет ожидать, пока сосед положит свою палочку. В программе наступает взаимная блокировка.