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

Что такое starvation?

2.7 Senior🔥 91 комментариев
#Многопоточность и асинхронность

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

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

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

Что такое Starvation (Голодание)?

Starvation (Голодание) — это ситуация в многопоточном программировании, когда один или несколько потоков не могут получить доступ к общим ресурсам или процессорному времени в течение длительного периода, в то время как другие потоки продолжают нормально выполняться. Это разновидность проблемы соревнования за ресурсы (resource contention), приводящая к несправедливому планированию и потенциальному "зависанию" части приложения.

В контексте Android-разработки starvation особенно критична, так как может напрямую влиять на отзывчивость UI (пользовательского интерфейса) и стабильность приложения. Основной UI-поток (Main Thread или UI Thread) должен всегда оставаться отзывчивым, и его блокировка из-за голодания приведёт к появлению диалога ANR (Application Not Responding).

Основные причины Starvation на Android

  1. Некорректная синхронизация: Чрезмерное или долгое удержание блокировок (synchronized, ReentrantLock без честного режима) одним потоком.
  2. Несправедливые политики планирования: Некоторые механизмы синхронизации (например, synchronized в Java) по умолчанию не гарантируют честность (fairness). Поток, ждущий дольше, не обязательно получит доступ следующим.
  3. Высокий приоритет "неважных" потоков: Потоки с высоким приоритетом могут постоянно вытеснять фоновые, но критически важные для UI задачи.
  4. Взаимоблокировки (Deadlocks) с частичным доступом: Хотя deadlock полностью останавливает потоки, частичные блокировки могут привести к голоданию других.
  5. Активные циклы ожидания (Busy Waiting): Поток постоянно потребляет процессорное время в цикле, не давая выполняться другим.

Пример кода, иллюстрирующий проблему

Рассмотрим пример с нечестной блокировкой, где низкоприоритетный поток может "голодать":

public class StarvationExample {
    // Общий ресурс, защищённый нечестной блокировкой (по умолчанию)
    private final Object lock = new Object();
    private int criticalCounter = 0;

    public void startHighPriorityWork() {
        new Thread(() -> {
            while (true) {
                synchronized (lock) { // Захват блокировки
                    criticalCounter++;
                    Log.d("Starvation", "HighPriority work: " + criticalCounter);
                    // Имитация долгой работы с ресурсом
                    try { Thread.sleep(100); } catch (InterruptedException e) {}
                } // Освобождение блокировки
                try { Thread.sleep(10); } catch (InterruptedException e) {}
            }
        }, "HighPriorityThread").start();
    }

    public void startLowPriorityWork() {
        new Thread(() -> {
            while (true) {
                synchronized (lock) { // Этот поток может очень долго ждать здесь
                    criticalCounter--;
                    Log.d("Starvation", "LowPriority work: " + criticalCounter);
                }
                try { Thread.sleep(10); } catch (InterruptedException e) {}
            }
        }, "LowPriorityThread").start();
    }
}

В этом примере HighPriorityThread часто и надолго захватывает блокировку lock. Поток LowPriorityThread будет постоянно находиться в состоянии ожидания, редко получая доступ к ресурсу, что и является starvation.

Методы предотвращения и борьбы с Starvation на Android

  • Использование честных (fair) блокировок: Класс ReentrantLock имеет конструктор с параметром fairness.
    private final ReentrantLock fairLock = new ReentrantLock(true); // true = fair lock
    public void accessResource() {
        fairLock.lock();
        try {
            // Работа с ресурсом
        } finally {
            fairLock.unlock();
        }
    }
    
  • Минимизация времени удержания блокировок: Критическую секцию внутри synchronized блока или под блокировкой нужно делать максимально короткой.
  • Использование высокоуровневых конкурентных структур из java.util.concurrent: Например, ConcurrentHashMap, LinkedBlockingQueue, семафоры (Semaphore), которые зачастую реализованы более эффективно и справедливо.
  • Правильное планирование задач: Использование ExecutorService с фиксированным или кастомным пулом потоков (ThreadPoolExecutor) позволяет управлять очередью задач и политиками отказа.
  • Приоритет Main Thread'а: Всегда выносите длительные операции (сеть, БД, сложные вычисления) в фоновые потоки, используя Kotlin Coroutines, RxJava или WorkManager. Это предотвращает голодание главного потока.
  • Анализ и мониторинг: Использование Android Profiler, особенно инструментов CPU и Energy, для выявления потоков, долго находящихся в состоянии BLOCKED или WAITING.

Различие Starvation и Deadlock

Важно не путать эти понятия:

  • Deadlock — это взаимная блокировка, где два или более потока ждут ресурсы, удерживаемые друг другом. Все вовлечённые потоки полностью остановлены. Ситуация статична и без внешнего вмешательства не разрешится.
  • Starvation — поток ждет ресурс, но этот ресурс периодически освобождается, однако его постоянно захватывают другие потоки. Голодающий поток всё ещё активен в состоянии ожидания, а система в целом продолжает работать.

Таким образом, starvation — это более коварная проблема, так как приложение может функционировать, но с деградацией производительности, периодическими фризами UI и сложно выявляемыми ошибками логики. Для Android-разработчика понимание и умение предотвращать голодание критически важно для создания плавных, отзывчивых и стабильных приложений.

Что такое starvation? | PrepBro