Thread en desarrollo móvil — qué es, tipos y gestión de hilos

Autor: IT Sectr Publicado: 2026-03-16 Tiempo de lectura: 11 min

Thread es la unidad básica de tiempo de CPU, con su propia pila y ejecutándose independientemente de otros hilos. En el desarrollo móvil, los hilos se utilizan para la ejecución paralela de tareas, manteniendo la interfaz receptiva durante operaciones largas. Android soporta java.lang.Thread, Executors y Kotlin Coroutines, iOS — Thread (Objective-C), GCD y OperationQueue. Según la Documentación de Android Thread, la creación de un hilo nativo requiere asignar ~1 MB para la pila por el sistema operativo.

Puntos Clave

  • Thread — la unidad mínima de planificación de CPU: cada hilo es independiente y tiene su propia pila
  • Crear un hilo requiere ~1 MB de pila en Android y 512 KB en iOS, por lo que los pools son más eficientes que la creación directa
  • Android: Thread, Executors, HandlerThread, Coroutines — cuatro niveles de abstracción de hilos
  • iOS: Thread (bajo nivel), GCD (DispatchQueue), OperationQueue (alto nivel)
  • Thread safety — el acceso compartido a datos mutables requiere sincronización: locks, atomic, colas seriales

Qué es Thread

Thread (hilo de ejecución) es una secuencia independiente de instrucciones que el sistema operativo puede planificar en un núcleo de CPU. Cada proceso (aplicación) contiene al menos un hilo — el Hilo Principal. Se crean hilos adicionales para la ejecución paralela de tareas. Cada hilo tiene su propia pila de programa (con variables locales), contador de programa (PC) y registros. La memoria heap es compartida entre todos los hilos del proceso.

En los sistemas operativos móviles, los hilos se planifican mediante multitarea preventiva: el SO puede interrumpir la ejecución de un hilo en cualquier momento y pasar el control a otro (cambio de contexto). El cambio de contexto es una operación costosa (1–10 microsegundos) ya que requiere guardar/restaurar registros de CPU, actualizar el TLB y vaciar los cachés. Por eso un número excesivo de hilos (cientos o miles) degrada el rendimiento — el SO dedica más tiempo a cambiar que a ejecutar.

Hilo vs Proceso — conceptos diferentes. Un proceso es una instancia de una aplicación con memoria virtual asignada. Un hilo dentro de un proceso comparte esta memoria con otros hilos. En Android, cada componente de la aplicación (Activity, Service, BroadcastReceiver) trabaja en un proceso pero puede ejecutarse en diferentes hilos. Una aplicación iOS también es un único proceso capaz de crear hilos adicionales a través de GCD o Thread.

Ciclo de Vida del Hilo: Estados y Transiciones

Cada hilo en Java/Kotlin (Android) y NSThread (iOS) pasa por cinco estados: New (creado), Runnable (listo para ejecutar), Running (ejecutándose en CPU), Blocked/Waiting (esperando un recurso o notificación), Terminated (finalizado). Las transiciones entre estados son gestionadas por el planificador del SO y los primitivos de sincronización. El desarrollador puede influir en la prioridad del hilo (Thread.setPriority()) y su estado (sleep, join, interrupt).

En Android, un hilo entra en estado Blocked al intentar adquirir un monitor ocupado (synchronized), llamar a Object.wait() o Thread.sleep(). En iOS — al llamar a NSCondition.wait(), pthread_cond_wait() o dispatch_semaphore_wait(). En estado Blocked, el hilo no consume CPU pero sigue ocupando memoria (pila). Un hilo puede ser interrumpido desde otro hilo, recibiendo InterruptedException (Java) o verificando isCancelled (Kotlin Coroutines).

EstadoDescripciónMétodo de Transición
NewHilo creado pero no iniciadoConstructor Thread()
RunnableHilo listo para ejecutar, esperando CPUthread.start()
RunningHilo ejecutándose en núcleo de CPUPlanificador del SO
Blocked/WaitingHilo esperando un recurso, monitor o notificaciónsynchronized, wait(), sleep()
TerminatedHilo completó run() o fue interrumpidorun() completado, interrupt()

Cambio de Contexto y su Costo

Cambio de contexto es una operación donde el SO guarda el estado del hilo actual (registros, PC, TLB) y carga el estado guardado de otro. En sistemas móviles (Linux + ART, XNU para iOS) un cambio de contexto toma 1–10 microsegundos. Si un hilo ejecuta una tarea en 100 microsegundos y un cambio de contexto toma 5, entonces se pierde el 5% del tiempo. Para minimizar los cambios de contexto, iOS usa GCD con work stealing, Android usa pools con fixedThreadCount.

Thread en Android: De Thread a Coroutines

Android ha evolucionado desde java.lang.Thread de bajo nivel hasta las corrutinas modernas. Cada nivel de abstracción proporciona más capacidades con menos sobrecarga. Thread es la clase base, pero no se recomienda su creación directa: un nuevo hilo no es gestionado por un pool, es difícil de monitorear y cancelar. AsyncTask (obsoleto desde API 30) fue un paso adelante, pero sufría de fugas de memoria y manejo incómodo de configuraciones.

HandlerThread es una subclase especial de Thread con Looper que puede procesar una cola de mensajes. Se usa para la ejecución secuencial de tareas en un hilo de fondo, por ejemplo, escribiendo datos en Room o archivos. HandlerThread se crea llamando a start(), después de lo cual se pueden enviar mensajes y Runnable a través de Handler(handlerThread.looper). Llamar a handlerThread.quit() detiene el Looper y termina el hilo.

kotlin
// Android: Thread, HandlerThread y Executors
import android.os.Handler
import android.os.HandlerThread
import java.util.concurrent.Executors

class ThreadExample {

    // 1. Creación directa de Thread (no recomendada)
    fun directThread() {
        val thread = Thread(Runnable {
            Thread.sleep(1000)
            print("Direct thread executed")
        })
        thread.start()
    }

    // 2. HandlerThread para tareas secuenciales en segundo plano
    fun handlerThreadExample() {
        val handlerThread = HandlerThread("BackgroundQueue")
        handlerThread.start()

        val handler = Handler(handlerThread.looper)
        handler.post {
            // Ejecución secuencial en un hilo de fondo
            Thread.sleep(500)
            print("HandlerThread: tarea completada")
        }

        // Detener el hilo (se ejecuta cuando las tareas terminan)
        handlerThread.quitSafely()
    }

    // 3. Executors — pool de hilos
    fun executorExample() {
        val executor = Executors.newFixedThreadPool(4)
        for (i in 1..10) {
            executor.execute {
                print("Task $i on thread ${Thread.currentThread().getName()}")
            }
        }
        executor.shutdown()
    }

    // 4. Kotlin Coroutines — estándar moderno
    suspend fun coroutineExample() = kotlinx.coroutines.withContext(
        kotlinx.coroutines.Dispatchers.Default
    ) {
        print("Coroutine on thread: ${Thread.currentThread().getName()}")
    }
}

El ejemplo ThreadExample muestra los cuatro niveles de abstracción de hilos en Android. La creación directa de Thread es el enfoque más bajo y menos eficiente. HandlerThread es útil para tareas secuenciales en segundo plano. Executors.newFixedThreadPool(4) crea un pool de 4 hilos para la ejecución paralela de hasta 10 tareas. Kotlin Coroutines con Dispatchers.Default es un enfoque moderno, eficiente y seguro.

HandlerThread: Tareas Secuenciales en Segundo Plano

HandlerThread es una subclase especializada de Thread con un Looper incorporado y cola de mensajes. Se crea llamando a start(), después de lo cual se pueden enviar Runnable y mensajes a través de Handler(handlerThread.looper). HandlerThread ejecuta tareas estrictamente secuenciales — la siguiente tarea no comienza hasta que la anterior se completa. Esto es útil para escribir datos en Room o archivos, donde el orden de las operaciones es crítico. Llamar a quitSafely() detiene el Looper después de que la tarea actual se completa.

Thread en iOS: Thread, GCD y OperationQueue

iOS también proporciona tres niveles de gestión de hilos. Thread (Thread en Swift, NSThread en Objective-C) es una API de bajo nivel que crea directamente un hilo nativo. GCD (Grand Central Dispatch) a través de DispatchQueue es la herramienta principal para los desarrolladores de iOS, gestionando automáticamente un pool de hilos. OperationQueue es una abstracción de alto nivel sobre GCD con soporte para dependencias, prioridades y cancelación.

El uso directo de Thread en el desarrollo moderno de iOS es extremadamente raro — GCD proporciona todas las capacidades necesarias con gestión automática de memoria e hilos. Thread se usa solo para casos específicos: almacenamiento local de hilo (threadDictionary), creación de un RunLoop para un hilo de fondo, o integración con bibliotecas C que esperan pthread_t.

swift
import Foundation

class ThreadManager {

    // 1. Thread (bajo nivel)
    func createThread() {
        let thread = Thread {
            // Código ejecutándose en un nuevo hilo
            print("Current thread: \(Thread.current)")
        }
        thread.name = "com.app.worker"
        thread.qualityOfService = .utility
        thread.start()
    }

    // 2. GCD — DispatchQueue
    func gcdExample() {
        // Cola concurrente
        let queue = DispatchQueue(label: "com.app.concurrent",
                                 qos: .utility,
                                 attributes: .concurrent)

        queue.async {
            print("GCD async task")
        }

        // Barrera para sincronización de escritura
        queue.async(flags: .barrier) {
            // Acceso exclusivo durante la escritura
            print("Barrier write: exclusive access")
        }
    }

    // 3. OperationQueue con dependencias
    func operationQueueExample() {
        let queue = OperationQueue()
        queue.maxConcurrentOperationCount = 2
        queue.qualityOfService = .background

        let download = BlockOperation {
            print("Downloading...")
        }
        let process = BlockOperation {
            print("Processing...")
        }
        let save = BlockOperation {
            print("Saving...")
        }

        // Dependencias: download -> process -> save
        process.addDependency(download)
        save.addDependency(process)

        queue.addOperations([download, process, save], waitUntilFinished: false)
    }
}

// Colección thread-safe mediante barrera GCD
class ThreadSafeArray<T> {
    private var array: [T] = []
    private let queue = DispatchQueue(label: "com.app.concurrent",
                                       attributes: .concurrent)

    var count: Int {
        return queue.sync { array.count } // concurrent read
    }

    func append(_ element: T) {
        queue.async(flags: .barrier) { // exclusive write
            self.array.append(element)
        }
    }
}

La clase ThreadSafeArray demuestra el patrón de Lectura Concurrente / Escrita Exclusiva mediante la barrera de GCD. La lectura a través de queue.sync{} se ejecuta en paralelo desde múltiples hilos. La escritura a través de queue.async(flags: .barrier) bloquea todas las demás operaciones (tanto lecturas como escrituras) hasta que la escritura se completa. Esto es más eficiente que los bloques sincronizados ya que no bloquea a los lectores cuando no hay escritura.

iOS Thread vs GCD: Cuándo usar Thread directamente

El uso directo de Thread en iOS se justifica en tres casos: para almacenamiento local de hilo (Thread.current.threadDictionary) — guardar datos vinculados a un hilo; para crear un RunLoop especial en un hilo de fondo con performSelector:onThread:; para integración con bibliotecas C/C++ que esperan pthread_t. En todos los demás casos, GCD a través de DispatchQueue es preferible — gestiona automáticamente el pool de hilos y el consumo de energía.

Sincronización de Hilos: Locks, Atomic, Colas Seriales

Race condition ocurre cuando dos o más hilos acceden simultáneamente a datos compartidos y al menos uno está escribiendo. El resultado depende del tiempo de ejecución y es impredecible. Se utilizan primitivos de sincronización para prevenir race conditions. En el desarrollo móvil, los primitivos disponibles incluyen locks (synchronized, NSLock), operaciones atómicas (AtomicInteger, propiedades atómicas de iOS) y colas (cola serial).

Selección del primitivo depende del escenario. Para contadores y banderas simples, las operaciones atómicas son suficientes (AtomicInteger, propiedad atómica). Para secciones críticas con múltiples operaciones — locks (synchronized, NSLock). Para estructuras de datos complejas — DispatchQueue serial o barrera GCD. Los locks son más fáciles de entender pero propensos a deadlocks y livelocks. Las colas son más complejas pero más seguras.

kotlin
// Sincronización en Android/Kotlin
import java.util.concurrent.atomic.AtomicInteger
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

class Counter {

    // 1. AtomicInteger — para contadores simples
    private val atomicCount = AtomicInteger(0)
    fun incrementAtomic() = atomicCount.incrementAndGet()

    // 2. synchronized — para secciones críticas
    @Synchronized
    fun synchronizedOperation() {
        // Solo un hilo a la vez
        doWork()
    }

    // 3. Mutex de corrutina — suspend-safe
    private val mutex = Mutex()
    suspend fun mutexOperation() {
        mutex.withLock {
            // protected code — thread-safe
            doWork()
        }
    }

    private fun doWork() { /* critical section */ }
}

// Ejemplo de Deadlock: A bloquea B, B bloquea A
class DeadlockExample {
    private val lockA = Any()
    private val lockB = Any()

    fun methodA() = synchronized(lockA) {
        Thread.sleep(100)
        synchronized(lockB) { print("OK") }
    }

    fun methodB() = synchronized(lockB) {
        Thread.sleep(100)
        synchronized(lockA) { print("OK") }
    }
}

Counter demuestra tres enfoques de sincronización. AtomicInteger.incrementAndGet() — operación atómica sin locks (CAS). @Synchronized — monitor incorporado de Java, bloquea todo el objeto. Mutex.withLock — mutex de corrutina, suspende la corrutina en lugar de bloquear el hilo (más eficiente). DeadlockExample muestra un deadlock clásico: dos hilos adquieren locks en diferente orden.

Pools de Hilos: Por qué Executors es mejor que Thread

Thread Pool es un conjunto de hilos precreados que se reutilizan para la ejecución de tareas. En lugar de crear un nuevo hilo para cada tarea (costoso), el pool toma un hilo libre del pool. Si no hay hilos libres, la tarea se pone en cola. El pool gestiona automáticamente su tamaño: se crean nuevos hilos bajo cargas pico, los hilos inactivos se terminan. Esto reduce la sobrecarga de creación de hilos en decenas de veces.

En Android, Executors.newFixedThreadPool(4) crea un pool de 4 hilos. Si llegan 10 tareas simultáneamente, 4 comienzan a ejecutarse inmediatamente, 6 esperan en la cola. Executors.newCachedThreadPool() crea hilos según sea necesario (sin límite) y termina los inactivos después de 60 segundos. Para iOS, GCD proporciona automáticamente pools de colas globales cuyo tamaño corresponde al número de núcleos de CPU y la carga actual.

En Kotlin Coroutines, los pools de hilos están ocultos dentro de los dispatchers. Dispatchers.Default usa un pool de tamaño = número de núcleos de CPU (mínimo 2). Dispatchers.IO — 64 hilos (suficiente para cientos de tareas IO-bound ya que la mayoría estará esperando E/S, sin ocupar CPU). Cada dispatcher escala automáticamente el pool bajo carga, ahorrando batería en inactividad.

Preguntas Frecuentes

¿Qué es un Thread en desarrollo móvil?

Thread es la unidad básica de ejecución de código en una aplicación. Cada proceso puede tener múltiples hilos que comparten memoria pero con su propia pila. En el desarrollo móvil, los hilos se utilizan para la ejecución paralela de tareas sin bloquear la UI. Android usa Thread, Executors, HandlerThread y Coroutines. iOS usa Thread, GCD (DispatchQueue) y OperationQueue.

¿Por qué no se recomienda crear Thread directamente?

Crear un Thread requiere asignar ~1 MB de pila en Android y ~512 KB en iOS — es una operación costosa. Para 1000 tareas, crear directamente 1000 hilos requeriría ~1 GB solo para pilas más la sobrecarga del cambio de contexto. En lugar de Thread, use pools (Executors, GCD) o corrutinas — reutilizan hilos, reduciendo la sobrecarga en decenas de veces.

¿Qué es una race condition y cómo evitarla?

Race condition es un comportamiento impredecible cuando múltiples hilos acceden simultáneamente a datos compartidos con escrituras. Se puede evitar de tres maneras: usar tipos atómicos (AtomicInteger), locks (synchronized, NSLock) o serializar el acceso a través de una cola (DispatchQueue serial, Actor en Kotlin). La mejor práctica es minimizar el estado mutable compartido y usar inmutabilidad.

¿En qué se diferencia Thread de corrutina?

Thread es un objeto nativo del sistema que ocupa ~1 MB de pila y está vinculado al núcleo del SO. Corrutina es una unidad de ejecución ligera de Kotlin que no está vinculada a un hilo específico y puede suspenderse sin bloquear. Un hilo puede ejecutar miles de corrutinas. Las corrutinas son más eficientes en memoria y permiten escribir código asíncrono sin callbacks.

¿Cómo detectar un deadlock en una aplicación móvil?

Deadlock se manifiesta como una congelación completa de la aplicación sin ANR. En Android, use Thread.getAllStackTraces() para volcar todas las pilas de hilos — dos hilos estarán esperando los locks del otro. En iOS — Thread.callStackSymbols. Herramientas: Android Studio Profiler (pestaña Threads), Instruments (iOS, Thread State View). Prevención: adquiera locks en un orden fijo, use tryLock con tiempo de espera.

Resumen

  • Thread — la unidad mínima de CPU: ejecución independiente con su propia pila, memoria heap compartida
  • Cinco estados de un hilo: New, Runnable, Running, Blocked/Waiting, Terminated
  • Android evolucionó de Thread → AsyncTask → Executors → HandlerThread → Coroutines
  • iOS proporciona Thread, GCD (DispatchQueue) y OperationQueue — de bajo a alto nivel
  • Race condition se resuelve con locks (synchronized, NSLock), tipos atómicos y colas seriales
  • Deadlock ocurre por adquisición cruzada de locks — se previene con orden fijo
  • Thread Pool es más eficiente que crear nuevos Threads: reutiliza hilos, reduce la sobrecarga del cambio de contexto

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