Что такое livelock в Java и чем он отличается от deadlock?

«Что такое livelock в Java и чем он отличается от deadlock?» — вопрос из категории Java Core, который задают на 22% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Livelock — это состояние многопоточной программы, при котором потоки активно выполняют работу (не заблокированы, как при deadlock), но не могут прогрессировать из-за циклической реакции на действия друг друга. Они постоянно меняют своё состояние в ответ на изменения других потоков, но не продвигаются к завершению задачи.

Ключевое отличие от deadlock:

  • Deadlock: Потоки заблокированы и ничего не делают, ожидая ресурсы.
  • Livelock: Потоки активны, "работают", но застряли в бесконечном цикле взаимных уступок или повторных попыток.

Типичный пример ("вежливые" потоки): Два потока пытаются пройти через узкий коридор, каждый отступает в сторону, чтобы пропустить другого, и они бесконечно меняются местами.

Пример кода на Java:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LivelockExample {
    private final Lock lock1 = new ReentrantLock(true); // Честная блокировка
    private final Lock lock2 = new ReentrantLock(true);

    public void operation1() {
        while (true) {
            if (lock1.tryLock()) {
                System.out.println("Поток 1: захватил lock1");
                try {
                    Thread.sleep(50); // Имитация работы
                    if (lock2.tryLock()) {
                        System.out.println("Поток 1: захватил lock2 -> УСПЕХ");
                        return; // Завершение работы
                    } else {
                        System.out.println("Поток 1: не получил lock2, отпускаю lock1");
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    lock1.unlock();
                }
            }
            // Короткая пауза перед повторной попыткой
            try { Thread.sleep(10); } catch (InterruptedException e) { break; }
        }
    }
    // Аналогичный метод operation2() для второго потока, но пытающийся захватить lock2, затем lock1.
}
// Запуск двух потоков с этими методами может привести к livelock.

Как избежать livelock:

  1. Ввести детерминированный порядок захвата ресурсов (всегда сначала lock1, потом lock2).
  2. Использовать случайные или возрастающие таймауты между повторными попытками.
  3. Перепроектировать логику взаимодействия потоков, чтобы избежать циклической зависимости.