Теперь, обладая минимальной теорией, давайте вернемся к задаче. В задаче требуется выполнить три метода по порядку, при этом каждый метод выполняется в отдельном потоке.
public void first() { print("first"); }
public void second() { print("second"); }
public void third() { print("third"); }
}
Конечно, сейчас вы можете возразить, что можно использовать корутины или RxJava. Но обычно на таких собеседованиях использовать библиотеки не рекомендуется, потому что проверяется насколько хорошо вы знаете java.util.concurrent.
Чтобы обеспечить последовательность выполнения, мы могли бы создать некоторые зависимости между парами методов, т. е. второй метод должен зависеть от завершения первого метода, а третий метод должен зависеть от завершения второго.Зависимость может быть реализована с помощью механизма параллелизма, как мы обсуждали в предыдущем разделе. Идея состоит в том, что мы могли бы использовать общую переменную с именем firstJobDone для координации порядка выполнения между первым и вторым методом. Аналогичным образом мы могли бы использовать другую переменную SecondJobDone, чтобы обеспечить порядок выполнения второго и третьего метода.
Алгоритм решения задачи следующий
1️⃣ Прежде всего, нам нужно создать координационные переменные firstJobDone и SecondJobDone, чтобы указать, что методы еще не выполнены.
2️⃣ В функции first() у нас нет зависимостей, поэтому мы можем сразу приступить к работе. В конце функции мы обновляем переменную firstJobDone, чтобы указать, что первый метод завершил работу.
3️⃣ В методе second() мы проверяем статус firstJobDone. Если не обновилось, то ждем, иначе переходим к выполнению логики метода. И в конце функции мы обновляем переменную secondJobDone, чтобы отметить завершение второго задания.
4️⃣ В методе third() мы проверяем статус secondJobDone. Как и в случае с методом second(), мы ждем сигнала secondJobDone, прежде чем приступить к выполнению третьего метода.
class Foo {
private AtomicInteger firstJobDone = new AtomicInteger(0);
private AtomicInteger secondJobDone = new AtomicInteger(0);
public Foo() {}
public void first(Runnable printFirst) throws InterruptedException {
printFirst.run();
// mark the first job as done, by increasing its count.
firstJobDone.incrementAndGet();
}
public void second(Runnable printSecond) throws InterruptedException {
while (firstJobDone.get() != 1) {
// waiting for the first job to be done.
}
printSecond.run();
// mark the second as done, by increasing its count.
secondJobDone.incrementAndGet();
}
public void third(Runnable printThird) throws InterruptedException {
while (secondJobDone.get() != 1) {
// waiting for the second job to be done.
}
printThird.run();
}
}
Очень важно не только решить задачку, но и уметь рассказать о плюсах и минусах решения.
Плюсы решения
✅ Lock-free: Отсутствие блокировок. Это решение позволяет избежать блокировок или явной блокировки, уменьшая потенциальную конкуренцию и повышая производительность в некоторых сценариях.
✅ Простота: Использование AtomicInteger делает реализацию простой и лаконичной.
Минусы решения
❌ Холостой цикл (Busy-wait): Цикл while работает вхолостую при этом потребляет циклы процессора. Это может быть уменьшено с помощью небольшой задержки(например, Thread.yield() или Thread.sleep())) внутри цикла.
❌ Проблемы с масштабированием: Холостой цикл может быть неэффективным при масштабировании до более сложных cценариев синхронизации.
Чтобы было удобнее прочитать все целиком с примерами и картинками, оформил все в статью в блоге
Ну а в следующем посте, если накидаете огоньков, разбавлю технические посты и расскажу о своих впечатлениях от посещения Сингапура и что меня больше всего шокировало в этом городе-государстве.