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

В чем разница между DeadLock и LiveLock?

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

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

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

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

DeadLock vs LiveLock: Различия и диагностика

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

DeadLock (Мертвая блокировка)

Определение

Situation где два или более потока навечно ждут друг друга и ни один не может продолжить работу. Приложение полностью застывает.

Классический пример: Банковский перевод

public class DeadLockExample {
    private Object account1 = new Object();
    private Object account2 = new Object();
    
    // Поток 1: Переводит со счета 1 на счет 2
    public void transfer1to2() {
        synchronized (account1) {                    // Thread-1 блокирует account1
            System.out.println("Thread-1 заблокировал account1");
            
            try { Thread.sleep(1000); } catch (Exception e) {}
            
            synchronized (account2) {                // Ждет account2...
                System.out.println("Thread-1 заблокировал account2");
            }
        }
    }
    
    // Поток 2: Переводит со счета 2 на счет 1
    public void transfer2to1() {
        synchronized (account2) {                    // Thread-2 блокирует account2
            System.out.println("Thread-2 заблокировал account2");
            
            try { Thread.sleep(1000); } catch (Exception e) {}
            
            synchronized (account1) {                // Ждет account1...
                System.out.println("Thread-2 заблокировал account1");
            }
        }
    }
}

// Выполнение:
// Thread-1: заблокировал account1
// Thread-2: заблокировал account2
// Thread-1: ждет account2 (держит account1)
// Thread-2: ждет account1 (держит account2)
// DEADLOCK! Оба потока зависли навечно

Диагностика DeadLock

# В консоли вы увидите, что приложение зависло
# Можно взять thread dump:
jps -l                                    # Найти PID процесса
jstack <PID> | grep -A 5 deadlock

# Результат:
# Found one Java-level deadlock:
# "Thread-2" waiting to lock monitor 0x00007f9d... (Object account1)
# which is held by "Thread-1"
# "Thread-1" waiting to lock monitor 0x00007f9d... (Object account2)
# which is held by "Thread-2"

Решение DeadLock

// Решение 1: Всегда закупывать ресурсы в одном порядке
public class FixedTransfer {
    private Object accountMin, accountMax;
    
    public void transfer() {
        // Определяем порядок (по ID, например)
        Object first = accountA.id < accountB.id ? accountA : accountB;
        Object second = accountA.id < accountB.id ? accountB : accountA;
        
        synchronized (first) {
            synchronized (second) {
                // Теперь все потоки захватывают в одинаковом порядке
                // Deadlock невозможен!
            }
        }
    }
}

// Решение 2: Использовать timeout
public void transferWithTimeout() {
    synchronized (account1) {
        long start = System.currentTimeMillis();
        boolean gotLock = false;
        
        // Ждем не более 1 секунды
        while (!gotLock && System.currentTimeMillis() - start < 1000) {
            if (account2.tryLock(10, TimeUnit.MILLISECONDS)) {
                gotLock = true;
                break;
            }
        }
        
        if (!gotLock) {
            System.out.println("Не удалось получить блокировку, откатываем операцию");
            return;
        }
        // ...
    }
}

// Решение 3: Использовать ReentrantLock с tryLock
priv ReentrantLock lock1 = new ReentrantLock();
private ReentrantLock lock2 = new ReentrantLock();

public void transferWithReentrantLock() {
    while (true) {
        if (lock1.tryLock()) {
            try {
                if (lock2.tryLock()) {
                    try {
                        // Выполняем операцию
                        return;
                    } finally {
                        lock2.unlock();
                    }
                }
            } finally {
                lock1.unlock();
            }
        }
        // Повторяем, если не удалось
    }
}

LiveLock (Живая блокировка)

Определение

Situation где потоки активно работают, но не делают прогресс. Потоки заняты, но результата нет.

Классический пример: Два человека в коридоре

public class LiveLockExample {
    public static class Person implements Runnable {
        private String name;
        private boolean bowl = false;  // есть ли миска
        private Person other;
        
        public Person(String name) {
            this.name = name;
        }
        
        public void setOther(Person other) {
            this.other = other;
        }
        
        @Override
        public void run() {
            while (!bowl) {
                System.out.println(name + " проверяет, есть ли миска...");
                
                if (other.bowl) {
                    System.out.println(name + " видит, что у другого есть миска. Даю ему дорогу.");
                    Thread.yield();  // Даю дорогу другому потоку
                    continue;
                }
                
                // Пытаемся взять миску
                if (!other.bowl && !bowl) {
                    bowl = true;
                    System.out.println(name + " взял миску!");
                }
            }
        }
    }
}

// Выполнение (LIVELOCK - оба активны, но ничего не меняется):
// Thread-1: проверяет, есть ли миска...
// Thread-2: проверяет, есть ли миска...
// Thread-1: видит, что у другого есть миска. Даю ему дорогу.
// Thread-2: видит, что у другого есть миска. Даю ему дорогу.
// [БЕСКОНЕЧНЫЙ ЦИКЛ - оба потока работают, но никто не делает прогресс]

Диагностика LiveLock

# LiveLock сложнее заметить - приложение не зависает, CPU использует 100%
# Но работа не выполняется

# Thread dump покажет потоки, которые постоянно меняют состояние:
jstack <PID>

# Вы увидите, что потоки постоянно переключаются между состояниями
# но не выполняют никакую полезную работу

Решение LiveLock

// Решение: Добавить случайную задержку (exponential backoff)
public class LiveLockFixed {
    private static final Random random = new Random();
    
    public void transfer() {
        while (true) {
            synchronized (account1) {
                if (synchronized (account2)) {
                    // Успешно получили оба замка
                    return;  // Делаем работу и выходим
                }
            }
            
            // Случайная задержка перед повтором
            int delay = random.nextInt(1, 10);
            Thread.sleep(delay);
            // Вероятность того, что оба потока снова столкнутся - очень мала
        }
    }
}

// Или использовать семафор для координации
private Semaphore semaphore = new Semaphore(1);

public void transferWithSemaphore() {
    semaphore.acquire();  // Только один поток может продолжить
    try {
        // Выполняем операцию
    } finally {
        semaphore.release();
    }
}

Таблица сравнения

Атрибут              | DeadLock              | LiveLock
--------------------------------------------------------------
Состояние           | Потоки зависли        | Потоки активны
CPU использование   | Нормальное (0%)       | Высокое (100%)
Прогресс            | Никакой               | Никакого
Заметность          | Сразу                 | Со временем
Причина             | Циклическая зависи.   | Попытка разрешить конфликт
Диагностика         | Thread dump           | Profiler + logs
Решение             | Отказ от взаимных     | Exponential backoff
                    | блокировок            | или semaphore

Практическое резюме

DeadLock:

  • Приложение замерзает полностью
  • Легко заметить в разработке
  • Но сложнее воспроизвести и отладить
  • Решение: упорядочить захват ресурсов или использовать timeout

LiveLock:

  • Приложение видимо работает (CPU 100%)
  • Сложнее заметить в production
  • Может появиться только под нагрузкой
  • Решение: добавить случайность/задержку или использовать семафоры

Оба явления стоит избегать правильным дизайном конкурентности: минимизируй блокировки, используй immutable объекты, предпочитай высокоуровневые конструкции (BlockingQueue, ConcurrentHashMap) вместо низкоуровневого synchronized.