Комментарии (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);
// Оба остаются голодными, хотя потоки активны!
}
}
Что происходит:
- Alice получает свой lock, проверяет Bob
- Bob получает свой lock, проверяет Alice
- Оба видят, что другой хочет есть, поэтому вежливо отступают
- Потоки постоянно пытаются снова → 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:
- Thread Dump — покажет, что потоки активны (state: RUNNABLE)
- CPU использование — потоки потребляют CPU, хотя не прогрессируют
- Счётчики — значения меняются, но результат не достигается
# Создаём 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
- Системы обработки очередей — когда очередь переполнена
- Распределённые системы — retry-логика может привести к livelock
- Балансировка нагрузки — потоки могут постоянно обмениваться работой
- Вежливые блокировки — когда потоки постоянно уступают друг другу
Livelock менее опасен чем Deadlock (хотя бы потому, что timeout может спасти), но всё равно нужно избегать. Правильное проектирование, timeouts и случайные backoffs помогут избежать этой проблемы.