Что такое race condition?
Race condition (состояние гонки) — это ошибка, возникающая в многопоточных или асинхронных программах, когда результат работы программы зависит от порядка исполнения потоков, и этот порядок не гарантирован.
Если два или более потока одновременно обращаются к общему ресурсу (переменной, файлу, объекту) и хотя бы один из них изменяет его, то поведение программы становится непредсказуемым. Это и есть race condition.
Простой пример на Kotlin
var counter = 0
fun increment() {
for (i in 1..1000) {
counter++
}
}
fun main() {
val t1 = Thread { increment() }
val t2 = Thread { increment() }
t1.start()
t2.start()
t1.join()
t2.join()
println("Counter = $counter")
}
Что ожидается:
-
Оба потока по 1000 раз увеличивают счётчик.
-
Значит, counter должен быть 2000.
Что получаем:
- Иногда 1980, иногда 2000, иногда 1957…
Почему?
Операция counter++ не атомарна:
-
Считать значение counter.
-
Увеличить его на 1.
-
Записать обратно.
→ Если два потока выполняют это одновременно, они могут перезаписать результат друг друга, и часть инкрементов теряется.
📌 Условия возникновения race condition
- **Несколько потоков
** - **Совместный доступ к одному ресурсу
** - **Хотя бы один поток модифицирует ресурс
** - **Отсутствие синхронизации
**
⚠️ Последствия
-
Непредсказуемое поведение.
-
Трудновоспроизводимые баги (иногда проявляются, иногда нет).
-
Повреждение состояния данных.
-
Уязвимости безопасности.
🔐 Как избежать race condition
1. Синхронизация доступа
Использование synchronized:
```python
@Synchronized
fun safeIncrement() {
counter++
}
Или явно:
```python
<br/><br/>val lock = Any()
synchronized(lock) {
counter++
}
2. Использование атомарных типов
Kotlin/Java:
<br/>val counter = AtomicInteger(0)
counter.incrementAndGet()
3. Изоляция состояния:
-
- Каждый поток работает со своим куском данных (например, через ThreadLocal).
4. Фреймворки с акторной моделью:
-
- Пример: kotlinx.coroutines.channels или StateFlow в Compose.
📦 Примеры в Android
-
Множественные потоки, изменяющие UI без runOnUiThread.
-
Одновременное сохранение/чтение SharedPreferences.
-
Доступ к SQLite из разных потоков без синхронизации.
-
Race condition между Activity.onDestroy() и результатами сетевых запросов.
🧠 Race condition vs Deadlock
Характеристика | Race condition | Deadlock |
---|---|---|
Что происходит | Результат зависит от порядка потоков | Потоки навсегда блокированы |
--- | --- | --- |
Опасность | Непредсказуемость, потеря данных | Заморозка приложения |
--- | --- | --- |
Проявление | Случайное, зависит от планировщика ОС | Стабильное зависание |
--- | --- | --- |
Решение | Синхронизация, атомарные операции | Контроль порядка блокировок, tryLock |
--- | --- | --- |
Итого
Race condition — это критическая ошибка, возникающая, когда несколько потоков одновременно работают с одним ресурсом без правильной координации. Она приводит к случайным багам, потерям данных и нестабильному поведению приложения. Чтобы избежать этого, следует грамотно синхронизировать доступ к общим ресурсам.