5. Синхронизировать вывод «Тик» и «Так» двумя потоками
Условие задачи:
Необходимо реализовать программу с двумя потоками:
первый поток выводит
Тик;второй поток выводит
Так;сообщения должны выводиться строго по очереди;
между соседними выводами должна проходить примерно одна секунда.
Ожидаемый результат:
Тик
Так
Тик
Так
Тик
Так
...
Спойлеры к решению
Подсказки
wait() / notifyAll().💡 Нужен флаг, определяющий, чей сейчас ход.
💡 Проверку условия перед
wait() следует выполнять через while, поскольку возможны ложные пробуждения.💡 Чтобы между
Тик и Так была пауза в одну секунду, следующий поток нельзя допускать к печати раньше времени.Решение
public class TickTock {
private final Object lock = new Object();
private boolean tickTurn = true;
public void tick() throws InterruptedException {
print("Тик", true);
}
public void tock() throws InterruptedException {
print("Так", false);
}
private void print(String text, boolean expectedTurn)
throws InterruptedException {
synchronized (lock) {
while (tickTurn != expectedTurn) {
lock.wait();
}
System.out.println(text);
// Не позволяем второму потоку напечатать сообщение
// раньше чем через одну секунду.
Thread.sleep(1000);
tickTurn = !tickTurn;
lock.notifyAll();
}
}
public static void main(String[] args)
throws InterruptedException {
TickTock tickTock = new TickTock();
Thread tickThread = new Thread(() -> {
try {
while (!Thread.currentThread().isInterrupted()) {
tickTock.tick();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
Thread tockThread = new Thread(() -> {
try {
while (!Thread.currentThread().isInterrupted()) {
tickTock.tock();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
tickThread.start();
tockThread.start();
}
}
Флаг:
private boolean tickTurn = true;
определяет, какой поток может выполнить печать.
Поток, для которого сейчас не его очередь, ожидает:
while (tickTurn != expectedTurn) {
lock.wait();
}
После вывода сообщения выполняется пауза:
Thread.sleep(1000);
Затем очередь передаётся другому потоку:
tickTurn = !tickTurn;
lock.notifyAll();
while, а не if, важен при работе с wait(): после пробуждения поток должен повторно проверить условие.
В данном учебном варианте sleep() выполняется внутри synchronized намеренно: пока текущий поток ждёт одну секунду, второй не сможет напечатать следующее сообщение раньше времени.
В исходной задаче одного wait() / notify() недостаточно — без отдельного признака очереди нельзя гарантировать, что первым будет именно Тик и что сообщения всегда будут строго чередоваться.