Starvation en aplicaciones móviles — qué es, causas y cómo prevenir la inanición de hilos

Autor: IT Sectr Publicado: 2026-03-18 Tiempo de lectura: 10 min

Starvation (inanición de hilo) es una situación en la que un hilo no puede acceder a un recurso necesario para continuar su trabajo, aunque esté listo para ejecutarse. Según Baeldung (Java Thread Starvation, 2024), la inanición surge debido a una planificación injusta, donde los hilos de baja prioridad se postergan constantemente en favor de los de mayor prioridad. A diferencia de Deadlock, Starvation no bloquea el hilo — permanece en estado RUNNABLE pero nunca obtiene tiempo de CPU.

Puntos Clave

  • Starvation — situación en la que un hilo no puede acceder a un recurso a pesar de estar listo para ejecutarse
  • A diferencia de Deadlock, un hilo en inanición permanece en estado RUNNABLE — no está bloqueado, pero no progresa
  • Planificación injusta (por ejemplo, sincronización con synchronized) — la causa principal de Starvation en la JVM
  • Fair Lock (ReentrantLock(true)) garantiza un orden FIFO justo de adquisición del bloqueo
  • Thread Priority en desarrollo móvil se recomienda no modificarla — Android Runtime gestiona las prioridades por sí misma

¿Qué es Starvation?

Starvation (inanición de hilo) es un problema de programación multihilo donde un hilo no puede acceder a un recurso necesario para completar su tarea, aunque el recurso no esté bloqueado permanentemente por otro hilo. El hilo está en estado RUNNABLE, pero el planificador o el mecanismo de sincronización pospone sistemáticamente su ejecución en favor de otros hilos.

En el desarrollo móvil, Starvation se manifiesta como una ejecución desigual de tareas: algunas operaciones se ejecutan al instante, mientras que otras sufren retrasos catastróficos. Por ejemplo, un hilo de fondo de sincronización de datos puede nunca obtener acceso a la base de datos si el hilo de la UI y los manejadores de animaciones lo adelantan constantemente. Según Android Developer Blog (Performance Matters, 2023), aproximadamente el 12% de los fotogramas perdidos (jank) en Android son causados por Starvation de tareas en segundo plano de las que depende el renderizado.

La diferencia clave entre Starvation y Deadlock es la reversibilidad. Si la carga del sistema disminuye o las prioridades se redistribuyen, el hilo en inanición puede adquirir el recurso y completar su trabajo. Sin embargo, bajo una carga alta sostenida, Starvation puede durar indefinidamente, creando la impresión de una aplicación congelada.

Causas de la inanición de hilos

Bloqueos injustos (Non-Fair Locks)

synchronized en Java y Kotlin es un ejemplo clásico de mecanismo injusto. Bajo alta contención, la JVM puede otorgar continuamente el bloqueo a los mismos hilos activos, mientras que otros hilos pierden constantemente la carrera. Esto no es un error de la JVM sino una compensación de diseño: los bloqueos injustos proporcionan mayor rendimiento a expensas de la equidad de acceso. Para aplicaciones móviles con 4–8 hilos, este problema es especialmente relevante.

Uso incorrecto de prioridades

Establecer diferentes prioridades de hilos puede provocar Starvation de los hilos de baja prioridad. En Android Runtime, el planificador CFS (Completely Fair Scheduler) de Linux distribuye el tiempo de CPU proporcionalmente a las prioridades, y si los hilos de alta prioridad están constantemente activos, los de baja prioridad pueden nunca obtener tiempo de CPU. Google desaconseja firmemente cambiar las prioridades de los hilos en Android — el sistema las gestiona por sí mismo.

Secciones críticas largas

Si un hilo mantiene un bloqueo durante demasiado tiempo (realizando cálculos pesados, solicitudes de red u operaciones de archivo dentro de un bloque synchronized), otros hilos que esperan ese bloqueo sufren inanición. Esto es especialmente peligroso en Android, donde las operaciones largas en el hilo de la UI causan ANR, y moverlas a hilos en segundo plano sin optimizar las secciones críticas solo transfiere el problema de Starvation a los hilos trabajadores.

Ejemplo de Starvation en código Kotlin

Considere un ejemplo donde un hilo adquiere un bloqueo con demasiada frecuencia debido a una planificación injusta. Starvation se demuestra mediante un bucle infinito de un hilo de alta prioridad que impide que un hilo de baja prioridad acceda a un recurso compartido.

kotlin
class SharedResource {
    private val lock = Any()

    fun criticalSection(id: String) {
        synchronized(lock) {
            println("$id obtuvo acceso")
            Thread.sleep(10)  // simulando trabajo
        }
    }
}

fun main() {
    val resource = SharedResource()

    // Hilo de alta prioridad — activo constantemente
    val highPriority = Thread {
        while (true) {
            resource.criticalSection("High")
        }
    }

    // Hilo de baja prioridad — puede nunca obtener acceso
    val lowPriority = Thread {
        while (true) {
            resource.criticalSection("Low")
        }
    }

    highPriority.start()
    lowPriority.start()
    // "Low" puede nunca imprimir un mensaje — ¡Starvation!
}

En este ejemplo, el hilo highPriority adquiere constantemente el bloqueo y lo libera solo por 10 ms. Debido a la naturaleza injusta de synchronized, el planificador de la JVM probablemente otorgará el bloqueo nuevamente al mismo hilo que acaba de liberarlo — el hilo de baja prioridad sufre inanición. La solución es usar ReentrantLock(true) con el indicador fair, que garantiza un orden FIFO de espera.

La versión corregida con un bloqueo justo asegura una distribución equitativa del acceso al recurso.

kotlin
class FairSharedResource {
    private val fairLock = ReentrantLock(true)  // fair = true

    fun criticalSection(id: String) {
        fairLock.lock()
        try {
            println("$id obtuvo acceso (fair)")
            Thread.sleep(10)
        } finally {
            fairLock.unlock()
        }
    }
}

Starvation vs Deadlock vs Livelock

Tres problemas clásicos de multihilo — Starvation, Deadlock y Livelock — a menudo se agrupan, pero sus mecanismos y soluciones difieren. Starvation — el hilo está listo pero no puede adquirir el recurso. Deadlock — los hilos están bloqueados por espera cíclica. Livelock — los hilos están activos pero no progresan.

ParámetroStarvationDeadlockLivelock
Estado del hiloRUNNABLEBLOCKEDRUNNABLE
ProgresoNingunoNingunoNinguno (aunque activo)
Uso de CPUBajoMínimoAlto (hasta 100%)
CausaPlanificación injustaEspera cíclicaMisma respuesta al conflicto
Solución principalFair Lock, acortar secciones críticasJerarquía de bloqueosLímite de reintentos, exponential backoff

Starvation se considera menos crítico que Deadlock porque no es fatal — bajo carga reducida, el hilo en inanición eventualmente se ejecutará. Sin embargo, en el uso real de Android, donde la memoria y la CPU son limitadas, Starvation puede durar minutos, creando una experiencia de usuario inaceptable.

Cómo detectar Starvation

Thread Dump tomado repetidamente a intervalos cortos — el método básico para detectar inanición. Si un hilo está consistentemente en estado RUNNABLE pero su pila de llamadas no cambia a través de múltiples volcados — esta es una señal clásica de Starvation. En Android Studio, use Android Profiler con grabación del estado de los hilos a lo largo del tiempo.

La detección automatizada es posible mediante monitoreo del tiempo de ejecución de tareas. Si una tarea con un tiempo de ejecución predecible (por ejemplo, 50 ms) tarda 5 segundos o más — hay una alta probabilidad de Starvation. En aplicaciones móviles, Firebase Performance Monitoring permite configurar trazas personalizadas para secciones críticas y recibir notificaciones cuando se superan los umbrales.

Para diagnosticar Starvation causada por bloques synchronized, use Java Flight Recorder (JFR) (disponible en Android a través de la API de OpenJDK) o Async Profiler. Estas herramientas muestran qué monitores tienen los tiempos de espera más altos y qué hilos compiten por cada monitor. Los datos de JFR se integran con IntelliJ IDEA Ultimate a través de su perfilador integrado.

Métodos para prevenir la inanición de hilos

Fair Lock (ReentrantLock con indicador true)

ReentrantLock(true) garantiza que los hilos adquieran el bloqueo en orden FIFO. A diferencia de synchronized, un bloqueo justo no permite que un hilo que acaba de liberar el bloqueo lo readquiera inmediatamente. Esto elimina completamente Starvation, aunque reduce el rendimiento general entre un 10–20% debido a la sobrecarga de mantener la cola.

Estructuras atómicas sin bloqueos

Estructuras de datos sin bloqueos (ConcurrentHashMap, AtomicReference, LongAdder) eliminan Starvation por definición, ya que no tienen bloqueos que puedan ser retenidos por un hilo. Todas las operaciones usan instrucciones CAS de la CPU que garantizan que al menos un hilo progrese en un número finito de pasos. Para desarrollo móvil, prefiera ConcurrentLinkedQueue para colas de tareas.

Secciones críticas cortas

Minimizar el tiempo de retención del bloqueo es una forma universal de reducir el riesgo de Starvation. Mueva operaciones pesadas (red, E/S de disco, cálculos complejos) fuera de los bloques synchronized. Use ReadWriteLock para escenarios donde los lectores no deben sufrir inanición debido a escritores poco frecuentes. La biblioteca Kotlin Coroutines proporciona Mutex con un mecanismo de suspensión que no bloquea un hilo del sistema operativo.

Variables de condición y señales

Condition.await() y signal() deben usarse con precaución: un hilo esperando en una Condition se despierta junto con otros hilos (spurious wakeup), y todos compiten por el bloqueo. Si un hilo regresa inmediatamente a espera después de await mientras otros logran adquirir el bloqueo, el hilo en inanición puede despertarse y volver a dormirse indefinidamente. Siempre verifique la condición en un bucle while en lugar de un if para garantizar la re-verificación.

Preguntas Frecuentes

¿Cuál es la diferencia entre Starvation e Inversión de Prioridad (Priority Inversion)?

Priority Inversion es una situación en la que un hilo de baja prioridad mantiene un bloqueo necesario para un hilo de alta prioridad. Como resultado, el hilo de alta prioridad espera al de baja prioridad — las prioridades se invierten. Starvation es un problema más amplio: un hilo no puede adquirir un recurso independientemente de la prioridad, debido a una planificación injusta o secciones críticas largas.

¿Puede ocurrir Starvation en una aplicación de un solo hilo?

No, Starvation es un problema de multihilo. El código de un solo hilo no tiene contención de recursos ni planificación de hilos. Sin embargo, Starvation puede ocurrir en código asíncrono de un solo hilo (por ejemplo, el bucle de eventos de JavaScript) si una microtarea pospone indefinidamente la ejecución de otras mediante setTimeout con retardo cero.

¿Cómo se relaciona el Modelo de Memoria de Java con Starvation?

JMM (Java Memory Model) define reglas para la visibilidad de cambios entre hilos pero no garantiza una planificación justa. synchronized, de acuerdo con JMM, asegura consistencia secuencial — corrección básica — pero no previene Starvation. La equidad requiere mecanismos adicionales no especificados en JMM.

¿Qué es Starvation en el hilo de UI de Android?

El hilo de UI (Main Thread) no puede sufrir inanición en el sentido clásico porque tiene la prioridad más alta. Sin embargo, Starvation ocurre cuando el hilo de UI espera un resultado de un hilo de fondo en inanición. Un escenario típico: un AsyncTask o corutina carga datos pero no puede acceder a la base de datos debido a contención con otros hilos, y la UI se congela mientras espera.

¿Cómo prevenir Starvation en Kotlin Coroutines?

En corutinas, para prevenir Starvation, use limitedParallelism en Dispatchers.IO para evitar el agotamiento de hilos. Para sincronización, use Mutex de kotlinx.coroutines.sync — suspende la corutina en lugar de bloquear el hilo, reduciendo el riesgo de inanición. Evite runBlocking en corutinas, ya que puede capturar un hilo del pool y causar Starvation de otras corutinas.

Resumen

  • Starvation — situación en la que un hilo está listo para ejecutarse pero no puede adquirir un recurso debido a una planificación injusta
  • A diferencia de Deadlock, un hilo en inanición permanece en estado RUNNABLE y puede ejecutarse cuando la carga disminuye
  • Bloqueos injustos (synchronized) y el uso incorrecto de prioridades son las principales causas de Starvation
  • Fair Lock (ReentrantLock con indicador true) garantiza un orden de acceso FIFO y elimina completamente la inanición
  • Estructuras sin bloqueos (ConcurrentHashMap, AtomicReference) eliminan Starvation a nivel de arquitectura
  • Thread Dump con capturas repetidas y Java Flight Recorder son métodos efectivos para diagnosticar Starvation
  • Secciones críticas cortas y ReadWriteLock reducen la probabilidad de inanición en sistemas de alta carga

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también