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

Что такое LiveLock?

2.4 Senior🔥 41 комментариев
#JVM и память#Многопоточность и асинхронность

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

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

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

LiveLock — блокировка без deadlock

LiveLock (живая блокировка) — это состояние, когда два или более потока активны и выполняют действия, но не могут сделать прогресс, потому что постоянно реагируют друг на друга. В отличие от deadlock, потоки не заблокированы — они работают, но бесполезно.

Отличие от DeadLock

DeadLock:

  • Потоки заблокированы и ждут друг друга
  • Никакой активности
  • Программа зависает

LiveLock:

  • Потоки активны и выполняют действия
  • Но не делают полезной работы
  • Постоянно реагируют друг на друга
  • Высокое потребление CPU

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

// Два потока бесконечно проверяют условие и отступают
var resource1Busy = false
var resource2Busy = false

// Поток 1
thread {
    while (true) {
        if (!resource1Busy && !resource2Busy) {
            resource1Busy = true
            // Начал работу
            delay(10)
            // Нужна resource2
            if (resource2Busy) {
                println("Поток 1: resource2 занята, отпускаю resource1")
                resource1Busy = false  // Вежливо отпустил
                delay(1)  // Небольшая задержка
            }
        }
    }
}

// Поток 2
thread {
    while (true) {
        if (!resource1Busy && !resource2Busy) {
            resource2Busy = true
            // Начал работу
            delay(10)
            // Нужна resource1
            if (resource1Busy) {
                println("Поток 2: resource1 занята, отпускаю resource2")
                resource2Busy = false  // Вежливо отпустил
                delay(1)  // Небольшая задержка
            }
        }
    }
}

// Результат: оба потока постоянно отпускают ресурсы
// и начинают заново, ничего не делая

Реальный пример: синхронизация сетевых запросов

class DataSync {
    var isSyncing = false
    var hasConflict = false
    
    suspend fun syncData() {
        while (true) {
            if (!isSyncing) {
                isSyncing = true
                try {
                    val localData = getLocalData()
                    val remoteData = getRemoteData()
                    
                    if (localData != remoteData) {
                        hasConflict = true
                        isSyncing = false  // Отпустил ресурс
                        delay(100)
                        // Другой поток тоже попытается синхронизировать
                        // Может быть бесконечный цикл попыток
                        continue
                    }
                    
                    uploadData(localData)
                    isSyncing = false
                    break
                } catch (e: Exception) {
                    isSyncing = false
                    delay(100)
                }
            } else {
                delay(10)
            }
        }
    }
}

Как обнаружить LiveLock

Признаки LiveLock:

  • CPU нагружен на 100%, но никакого прогресса
  • Логи показывают повторяющиеся действия
  • Приложение не зависает, но не продвигается вперёд
  • Потоки выполняют миллионы операций в секунду без результата
// Профилирование показывает активность
val startTime = System.currentTimeMillis()
var iterations = 0

while (System.currentTimeMillis() - startTime < 5000) {
    // Попытки синхронизации
    iterations++
}

println("Итераций за 5 сек: $iterations")  // Огромное число!

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

1. Добавить случайную задержку:

thread {
    while (true) {
        if (!resource1Busy && !resource2Busy) {
            resource1Busy = true
            // Работа
            if (resource2Busy) {
                resource1Busy = false
                // Важно: случайная задержка разбивает синхронизацию
                delay((Math.random() * 100).toLong())
                continue
            }
        }
    }
}

2. Использовать явный порядок захватывания ресурсов:

// Всегда захватываем resource1, затем resource2
lock(resource1) {
    lock(resource2) {
        // Безопасная работа с обоими ресурсами
    }
}

3. Использовать retry с exponential backoff:

suspend fun syncWithRetry(maxAttempts: Int = 5) {
    var attempt = 0
    var delay = 100L
    
    while (attempt < maxAttempts) {
        try {
            sync()
            return  // Успех
        } catch (e: ConflictException) {
            attempt++
            delay(delay)
            delay *= 2  // Exponential backoff
        }
    }
}

4. Использовать Mutex с timeout:

val mutex = Mutex()

suspend fun safeSyncData() {
    if (mutex.tryLock(timeoutMillis = 1000)) {
        try {
            performSync()
        } finally {
            mutex.unlock()
        }
    } else {
        println("Не смог захватить блокировку")
    }
}

В контексте Android

LiveLock может возникнуть при:

  • Синхронизации данных между несколькими источниками
  • Конфликтах при работе с БД (Room, SQLite)
  • Неправильной обработке сетевых ошибок
  • Взаимных уведомлениях между observers

Заключение

Livelock — это подвид проблемы concurrency, менее очевидный чем deadlock, но не менее опасный. Ключ к решению — предсказуемость порядка действий и введение случайности или явных временных интервалов для разрешения конфликтов.