← Назад к вопросам

Что такое Livelock в Java?

2.3 Middle🔥 171 комментариев
#Многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Livelock в Java

Livelock — это состояние, когда потоки остаются активными, но не могут прогрессировать в своей работе. Потоки постоянно реагируют друг на друга, но никогда не достигают желаемого состояния. В отличие от Deadlock, потоки не заблокированы, а активно что-то делают.

Отличие от Deadlock

Deadlock (тупик):

  • Потоки заблокированы
  • Ждут друг друга
  • Состояние неизменно
// Deadlock: T1 ждёт lock2, T2 ждёт lock1
Object lock1 = new Object();
Object lock2 = new Object();

// Поток 1
synchronized(lock1) {
    synchronized(lock2) { } // ждёт lock2
}

// Поток 2
synchronized(lock2) {
    synchronized(lock1) { } // ждёт lock1 - DEADLOCK!
}

Livelock (живая блокировка):

  • Потоки активны
  • Они работают, но не прогрессируют
  • Состояние постоянно меняется

Классический пример Livelock

public class LivelockExample {
    static class Person {
        private String name;
        private boolean wantToDine = true;
        
        public Person(String name) {
            this.name = name;
        }
        
        public void eatWith(Person other) {
            while (wantToDine) {
                synchronized(this) {
                    synchronized(other) {
                        if (other.wantToDine) {
                            System.out.println(name + " ест с " + other.name);
                            wantToDine = false; // finished eating
                        }
                    }
                }
                // Вежливо уступаем другому потоку
                Thread.yield();
            }
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        Person alice = new Person("Alice");
        Person bob = new Person("Bob");
        
        // Alice попытается поесть с Bob
        new Thread(() -> alice.eatWith(bob)).start();
        
        // Bob попытается поесть с Alice
        new Thread(() -> bob.eatWith(alice)).start();
        
        Thread.sleep(5000);
        System.out.println("Alice голодна: " + alice.wantToDine);
        System.out.println("Bob голодна: " + bob.wantToDine);
        // Оба остаются голодными, хотя потоки активны!
    }
}

Что происходит:

  1. Alice получает свой lock, проверяет Bob
  2. Bob получает свой lock, проверяет Alice
  3. Оба видят, что другой хочет есть, поэтому вежливо отступают
  4. Потоки постоянно пытаются снова → LIVELOCK

Пример с двумя потоками и очередью

public class LivelockWithQueue {
    static class Worker implements Runnable {
        private BlockingQueue<Integer> inputQueue;
        private BlockingQueue<Integer> outputQueue;
        private String name;
        
        public Worker(BlockingQueue<Integer> input, BlockingQueue<Integer> output, String name) {
            this.inputQueue = input;
            this.outputQueue = output;
            this.name = name;
        }
        
        @Override
        public void run() {
            while (true) {
                try {
                    Integer item = inputQueue.poll(100, TimeUnit.MILLISECONDS);
                    if (item != null) {
                        // Если очередь на выходе полна, вернём обратно
                        if (outputQueue.offer(item, 100, TimeUnit.MILLISECONDS)) {
                            System.out.println(name + " обработал " + item);
                        } else {
                            // Очередь полна, вернём обратно
                            inputQueue.put(item);
                            System.out.println(name + " возвращает " + item);
                        }
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }
    }
}

Здесь потоки активны, но если обе очереди переполнены, они будут постоянно возвращать элементы туда-сюда → LIVELOCK.

Как выявить Livelock

Отличие от Deadlock:

  1. Thread Dump — покажет, что потоки активны (state: RUNNABLE)
  2. CPU использование — потоки потребляют CPU, хотя не прогрессируют
  3. Счётчики — значения меняются, но результат не достигается
# Создаём thread dump
jstack <pid> > thread_dump.txt

# Deadlock будет отмечен как "Found one Java-level deadlock"
# Livelock будет показан как RUNNABLE потоки, которые не завершаются

Как избежать Livelock

1. Добавьте случайный backoff

public void eatWith(Person other) {
    while (wantToDine) {
        synchronized(this) {
            synchronized(other) {
                if (other.wantToDine) {
                    System.out.println(name + " ест");
                    wantToDine = false;
                }
            }
        }
        // Вместо yield(), добавим случайную задержку
        try {
            Thread.sleep((long)(Math.random() * 100));
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

2. Установите timeout

BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10);

try {
    Integer item = inputQueue.poll(100, TimeUnit.MILLISECONDS);
    boolean success = outputQueue.offer(item, 100, TimeUnit.MILLISECONDS);
    
    if (!success) {
        // Не лезем в очередь обратно сразу, даём время другим потокам
        Thread.sleep(500 + (long)(Math.random() * 500));
    }
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}

3. Измените логику

public void eatWith(Person other) {
    while (wantToDine && other.wantToDine) {
        synchronized(this) {
            synchronized(other) {
                // Не вежливо отступаем, просто едим!
                if (other.wantToDine) {
                    System.out.println(name + " ест с " + other.name);
                    wantToDine = false;
                }
            }
        }
    }
}

4. Используйте ReentrantLock с timeouts

ReentrantLock lock1 = new ReentrantLock();
ReentrantLock lock2 = new ReentrantLock();

if (lock1.tryLock(100, TimeUnit.MILLISECONDS)) {
    try {
        if (lock2.tryLock(100, TimeUnit.MILLISECONDS)) {
            try {
                // Критическая секция
            } finally {
                lock2.unlock();
            }
        }
    } finally {
        lock1.unlock();
    }
}

Где встречается Livelock

  1. Системы обработки очередей — когда очередь переполнена
  2. Распределённые системы — retry-логика может привести к livelock
  3. Балансировка нагрузки — потоки могут постоянно обмениваться работой
  4. Вежливые блокировки — когда потоки постоянно уступают друг другу

Livelock менее опасен чем Deadlock (хотя бы потому, что timeout может спасти), но всё равно нужно избегать. Правильное проектирование, timeouts и случайные backoffs помогут избежать этой проблемы.

Что такое Livelock в Java? | PrepBro