Gymterview
middle

Что такое livelock?

Livelock (активная блокировка) – это разновидность взаимной блокировки, при которой потоки формально не заблокированы (их состояние не BLOCKED и не WAITING), но они не могут продвинуться дальше, потому что бесконечно реагируют на действия друг друга. В отличие от deadlock, потоки продолжают выполнять работу, но эта работа бесполезна – КПД системы падает до нуля.

Как возникает livelock

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

Характеристика Deadlock Livelock
Состояние потоков BLOCKED / WAITING RUNNABLE
Потребление CPU Нулевое Высокое (бесполезная работа)
Обнаружение JVM Возможно через ThreadMXBean Автоматически не обнаруживается
Внешние признаки Приложение «зависло» Приложение «крутится», но не продвигается

Пример livelock

Пример: два потока уступают друг другу ложку
public class LivelockExample {
    static class Spoon {
        private Diner owner;

        Spoon(Diner owner) { this.owner = owner; }

        synchronized void use() {
            System.out.println(owner.name + " ест!");
        }

        synchronized void setOwner(Diner newOwner) {
            this.owner = newOwner;
        }

        synchronized Diner getOwner() { return owner; }
    }

    static class Diner {
        final String name;
        boolean isHungry = true;

        Diner(String name) { this.name = name; }

        void eatWith(Spoon spoon, Diner partner) {
            while (isHungry) {
                // Если ложка не у меня — жду
                if (spoon.getOwner() != this) {
                    try { Thread.sleep(1); } catch (InterruptedException e) { return; }
                    continue;
                }
                // Если партнёр тоже голоден — уступаю (вежливость!)
                if (partner.isHungry) {
                    System.out.println(name + ": "
                        + partner.name + " тоже голоден, уступаю ложку.");
                    spoon.setOwner(partner);
                    continue; // <-- Livelock: оба бесконечно уступают
                }
                // Партнёр сыт — ем
                spoon.use();
                isHungry = false;
                spoon.setOwner(partner);
            }
        }
    }

    public static void main(String[] args) {
        Diner alice = new Diner("Алиса");
        Diner bob = new Diner("Боб");
        Spoon spoon = new Spoon(alice);

        new Thread(() -> alice.eatWith(spoon, bob)).start();
        new Thread(() -> bob.eatWith(spoon, alice)).start();
        // Оба потока бесконечно уступают друг другу ложку
    }
}

Способы предотвращения livelock

  1. Случайная задержка (jitter) – добавить случайный интервал перед повторной попыткой, чтобы потоки «рассинхронизировались»:
    Пример
    Thread.sleep(ThreadLocalRandom.current().nextInt(10, 100));
    
  2. Приоритетная схема – ввести строгий порядок: один поток всегда имеет приоритет при конфликте.
  3. Ограничение числа повторов – если поток уступил N раз подряд, он прекращает уступать.
  4. Использование Lock.tryLock(timeout) – вместо бесконечного цикла ставить таймаут и обрабатывать ситуацию невозможности захвата.

Аналогия из жизни. Два человека встречаются в узком коридоре. Каждый, пытаясь быть вежливым, отходит в сторону – но оба отходят в одну и ту же сторону. Они бесконечно двигаются из стороны в сторону, не продвигаясь в нужном направлении. Решение – один из них останавливается и пропускает другого.

На собеседовании. Типичный вопрос: «Чем livelock отличается от deadlock?» Ключевое отличие: при deadlock потоки заблокированы и не потребляют CPU, при livelock потоки активны (RUNNABLE), потребляют CPU, но не совершают полезной работы. Livelock сложнее обнаружить, потому что приложение выглядит «живым». Часто livelock возникает как побочный эффект неудачной реализации предотвращения deadlock.