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

В каких случаях может произойти взаимная блокировка

1.8 Middle🔥 141 комментариев
#Многопоточность и асинхронность

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

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

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

Взаимная блокировка (Deadlock) в Android и многопоточной разработке

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

Основные условия возникновения deadlock (Условия Кофмана)

Deadlock возможен при одновременном выполнении четырех условий:

  1. Взаимное исключение (Mutual Exclusion) — ресурс не может быть использован несколькими потоками одновременно.
  2. Удержание и ожидание (Hold and Wait) — поток, удерживая один ресурс, ожидает получения другого ресурса от другого потока.
  3. Неотъемлемость (No Preemption) — ресурс нельзя принудительно отнять у потока, он может быть освобожден только добровольно.
  4. Круговое ожидание (Circular Wait) — возникает циклическая зависимость, где поток A ожидает ресурс от потока B, а поток B — от потока A.

Типичные сценарии взаимной блокировки в Android

1. Блокировки на разных объектах в неправильном порядке

Наиболее частая причина. Если два потока пытаются захватить несколько блокировок, но делают это в разной последовательности, может возникнуть circular wait.

// Поток 1 выполняет этот код
fun thread1Logic() {
    synchronized(lockA) {
        synchronized(lockB) {
            // Работа с ресурсами
        }
    }
}

// Поток 2 выполняет этот код
fun thread2Logic() {
    synchronized(lockB) {
        synchronized(lockA) {
            // Работа с ресурсами
        }
    }
}

Если Thread 1 захватил lockA и ждет lockB, а Thread 2 захватил lockB и ждет lockA — deadlock неизбежен. Решение: всегда захватывать блокировки в фиксированном глобальном порядке (например, сначала A, потом B).

2. Deadlock в GUI и системных вызовах

На Android, работая с UI, можно столкнуться с ситуацией, когда фоновый поток ожидает результата от UI потока (например, через runOnUiThread), который в свою очередь блокируется ожиданием того же фонового потока. Особенно рискованно смешивать synchronized, Handler и Looper.

// Пример рискованной конструкции
class RiskyActivity : Activity() {
    private val backgroundLock = Any()
    private var uiResult: String? = null

    fun riskyCall() {
        // Фоновый поток захватывает свою блокировку и вызывает UI
        synchronized(backgroundLock) {
            runOnUiThread {
                // UI поток теперь пытается захватить ту же блокировку
                synchronized(backgroundLock) {
                    uiResult = "Done"
                }
            }
            // Фоновый поток ждет, пока UI поток установит результат
            while (uiResult == null) {
                Thread.sleep(10)
            }
        }
    }
}

3. Блокировки при использовании synchronized с вызовом внешних методов

Если внутри synchronized блока вызывается метод другого объекта, который тоже может содержать синхронизацию, непредвиденная цепочка может привести к circular wait.

class ClassA {
    fun methodA() {
        synchronized(this) {
            // Вызов метода другого класса, который тоже синхронизирован
            ClassB().methodB()
        }
    }
}

class ClassB {
    fun methodB() {
        synchronized(this) {
            // Вызов обратно в ClassA
            ClassA().someOtherMethod()
        }
    }
}

4. Deadlock в работе с LiveData, Coroutines и RxJava

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

// Пример с coroutines и Dispatchers
suspend fun dangerousFlow() {
    withContext(Dispatchers.IO) {
        // Блокирующая операция на IO
        val result1 = networkCall()
        // Переключаемся на Main и ждем другую coroutine
        withContext(Dispatchers.Main) {
            // Эта coroutine может зависеть от результата другой,
            // которая тоже ждет на Main
            anotherSuspendFunction() // Если она тоже ждет нас — deadlock
        }
    }
}

Как предотвращать взаимные блокировки в Android

  • Строгий порядок блокировок: Всегда устанавливайте и соблюдайте глобальный порядок захвата ресурсов.
  • Использование tryLock с таймаутом: Вместо synchronized для ReentrantLock можно использовать tryLock(timeout), что позволяет избежать бесконечного ожидания.
  • Минимизация области синхронизации: Держите synchronized блоки как короткие и простые, избегайте вызова внешних методов внутри них.
  • Анализ зависимостей в асинхронном коде: При использовании Coroutines, RxJava или CompletableFuture избегайте циклических зависимостей между задачами.
  • Инструменты мониторинга: Используйте StrictMode для обнаружения длительных блокировок на UI потоке и анализ трассировки стека (jstack или Android Studio Profiler) при подозрении на deadlock.

Взаимная блокировка — это часто следствие недостаточного планирования многопоточного взаимодействия. В Android, где UI поток особенно критичен, deadlock может привести к ANR (Application Not Responding). Поэтому важно не только знать условия возникновения, но и применять профилактические меры при проектировании многопоточных компонентов.

В каких случаях может произойти взаимная блокировка | PrepBro