34. Обеспечить чередование вывода foo и bar
Условие задачи:
Дан класс FooBar. Одна и та же его инстанция используется двумя потоками:
поток A вызывает метод
foo();поток B вызывает метод
bar().
Каждый метод должен выполнить вывод n раз.
Необходимо синхронизировать потоки так, чтобы строки foo и bar выводились строго по очереди:
foobarfoobar...
Всего последовательность foobar должна повториться ровно n раз.
Код:
class FooBar {
private final int n;
public FooBar(int n) {
this.n = n;
}
public void foo() throws InterruptedException {
for (int i = 0; i < n; i++) {
System.out.print("foo");
}
}
public void bar() throws InterruptedException {
for (int i = 0; i < n; i++) {
System.out.print("bar");
}
}
}
Примеры:
n = 1
→ foobar
n = 2
→ foobarfoobar
n = 5
→ foobarfoobarfoobarfoobarfoobar
Спойлеры к решению
Подсказки
Semaphore.💡 Для
foo создай семафор с одним разрешением, чтобы этот поток мог начать первым.💡 Для
bar создай семафор с нулём разрешений, чтобы он сначала ожидал.💡 После вывода
foo нужно разрешить работу bar, а после bar — снова разрешить foo.Решение
class FooBar {
private final int n;
private final Semaphore fooSemaphore = new Semaphore(1);
private final Semaphore barSemaphore = new Semaphore(0);
public FooBar(int n) {
this.n = n;
}
public void foo() throws InterruptedException {
for (int i = 0; i < n; i++) {
fooSemaphore.acquire();
System.out.print("foo");
barSemaphore.release();
}
}
public void bar() throws InterruptedException {
for (int i = 0; i < n; i++) {
barSemaphore.acquire();
System.out.print("bar");
fooSemaphore.release();
}
}
}
Начальные значения семафоров:
new Semaphore(1);
new Semaphore(0);
означают, что foo() может начать работу сразу, а bar() должен ждать.
Последовательность для первой итерации выглядит так:
fooSemaphore = 1
barSemaphore = 0
foo:
acquire() → 0
print("foo")
barSemaphore.release() → 1
bar:
acquire() → 0
print("bar")
fooSemaphore.release() → 1
После этого цикл повторяется.
Таким образом, даже если поток bar запустится раньше потока foo, он остановится на:
barSemaphore.acquire();
поскольку изначально разрешений у barSemaphore нет.
Для n = 3 гарантируется порядок:
foo → bar → foo → bar → foo → bar
и результат:
foobarfoobarfoobar
Методы acquire() и release() обеспечивают необходимую синхронизацию и видимость изменений между потоками.
Для каждой итерации выполняется константное число операций, поэтому общая сложность — O(n). Дополнительная память — O(1).
Если один из потоков будет прерван и завершит работу раньше второго, второй поток потенциально может остаться ждать разрешения. Для базовой задачи это обычно не требуется обрабатывать отдельно, но в production-коде стоит предусмотреть общий механизм остановки обоих потоков.