Thread v mobilním vývoji — co to je, druhy a správa vláken

Autor: IT Sectr Publikováno: 2026-03-16 Doba čtení: 11 min

Thread — základní jednotka procesorového času, která má vlastní zásobník a běží nezávisle na ostatních vláknech. V mobilním vývoji se vlákna používají pro paralelní provádění úloh, aby rozhraní zůstalo responzivní během dlouhých operací. Android podporuje java.lang.Thread, Executors a Kotlin Coroutines, iOS — Thread (Objective-C), GCD a OperationQueue. Podle Android Thread Documentation vyžaduje vytvoření nativního vlákna alokaci ~1 MB pro zásobník operačním systémem.

Hlavní body

  • Thread — minimální jednotka plánování CPU: každé vlákno je nezávislé a má vlastní zásobník
  • Vytvoření vlákna vyžaduje ~1 MB pro zásobník v Androidu a 512 KB v iOS, proto jsou fondy efektivnější než přímé vytváření
  • Android: Thread, Executors, HandlerThread, Coroutines — čtyři úrovně abstrakce vláken
  • iOS: Thread (nízkoúrovňový), GCD (DispatchQueue), OperationQueue (vysokoúrovňový)
  • Thread safety — sdílený přístup k mutable datům vyžaduje synchronizaci: locks, atomic, serial queues

Co je Thread

Thread (vlákno provádění) — nezávislá posloupnost instrukcí, kterou může operační systém naplánovat na jádro CPU. Každý proces (aplikace) obsahuje alespoň jedno vlákno — Main Thread. Další vlákna se vytvářejí pro paralelní provádění úloh. Každé vlákno má vlastní programový zásobník (s lokálními proměnnými), čítač instrukcí (PC) a registry. Paměť haldy (heap) je společná pro všechna vlákna procesu.

V mobilních operačních systémech jsou vlákna plánována preemptivním multitaskingem: operační systém může provádění vlákna kdykoli přerušit a předat řízení jinému (context switch). Přepnutí kontextu je nákladná operace (1–10 mikrosekund), protože vyžaduje uložení/obnovení registrů CPU, aktualizaci TLB a vyčištění mezipamětí. Právě proto nadměrný počet vláken (stovky a tisíce) zhoršuje výkon — operační systém tráví více času přepínáním než prováděním.

Vlákno a proces jsou různé pojmy. Proces je instance aplikace s vyhrazenou virtuální pamětí. Vlákno uvnitř procesu sdílí tuto paměť s ostatními vlákny. V Androidu každá komponenta aplikace (Activity, Service, BroadcastReceiver) pracuje v jednom procesu, ale může běžet v různých vláknech. Aplikace iOS je také jedním procesem s možností vytvářet další vlákna přes GCD nebo Thread.

Životní cyklus vlákna: stavy a přechody

Každé vlákno v Java/Kotlin (Android) a NSThread (iOS) prochází pěti stavy: New (vytvořeno), Runnable (připraveno k provádění), Running (běží na CPU), Blocked/Waiting (čeká na zdroj nebo oznámení), Terminated (ukončeno). Přechody mezi stavy řídí plánovač operačního systému a synchronizační primitiva. Vývojář může ovlivnit prioritu vlákna (Thread.setPriority()) a jeho stav (sleep, join, interrupt).

V Androidu přechází vlákno do stavu Blocked při pokusu o získání obsazeného monitoru (synchronized), při volání Object.wait() nebo Thread.sleep(). V iOS — při volání NSCondition.wait(), pthread_cond_wait() nebo dispatch_semaphore_wait(). Ve stavu Blocked vlákno nespotřebovává CPU, ale zabírá paměť (zásobník). Vlákno může být přerušeno (interrupted) z jiného vlákna, obdrží InterruptedException (Java) nebo kontrolou isCancelled (Kotlin Coroutines).

Stav Popis Metoda přechodu
New Vlákno je vytvořeno, ale nebylo spuštěno Konstruktor Thread()
Runnable Vlákno je připraveno k provádění, čeká na CPU thread.start()
Running Vlákno běží na jádře CPU Plánovač operačního systému
Blocked/Waiting Vlákno čeká na zdroj, monitor nebo oznámení synchronized, wait(), sleep()
Terminated Vlákno dokončilo run() nebo bylo přerušeno run() dokončeno, interrupt()

Context Switch a jeho cena

Context switch (přepnutí kontextu) — operace, při které operační systém uloží stav aktuálního vlákna (registry, PC, TLB) a načte uložený stav jiného. V mobilních systémech (Linux + ART, XNU pro iOS) trvá context switch 1–10 mikrosekund. Pokud vlákno provádí úlohu za 100 mikrosekund a context switch trvá 5, ztrácí se 5 % času. Pro minimalizaci context switch používá iOS GCD s work stealing, Android — fondy s fixedThreadCount.

Thread v Androidu: od Threadu po Coroutines

Android prošel evolucí od nízkoúrovňového java.lang.Thread po moderní korutiny. Každá úroveň abstrakce dává více možností s menší režií. Thread je základní třída, ale jeho přímé vytváření se nedoporučuje: nové vlákno není spravováno fondem, je obtížné ho sledovat a rušit. AsyncTask (deprecated od API 30) byl krokem vpřed, ale trpěl úniky paměti a nepohodlným zpracováním konfigurací.

HandlerThread — speciální podtřída Thread s Looper, která může zpracovávat frontu zpráv. Používá se pro sekvenční provádění úloh na vlákně na pozadí, například zápis dat do Room nebo souborů. HandlerThread se vytváří voláním start(), poté lze přes Handler(handlerThread.looper) odesílat zprávy a Runnable. Volání handlerThread.quit() zastaví Looper a ukončí vlákno.

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

class ThreadExample {

    // 1. Přímé vytvoření Thread (nedoporučuje se)
    fun directThread() {
        val thread = Thread(Runnable {
            Thread.sleep(1000)
            print("Přímé vlákno vykonáno")
        })
        thread.start()
    }

    // 2. HandlerThread pro sekvenční úlohy na pozadí
    fun handlerThreadExample() {
        val handlerThread = HandlerThread("BackgroundQueue")
        handlerThread.start()

        val handler = Handler(handlerThread.looper)
        handler.post {
            // Sekvenční provádění na vlákně na pozadí
            Thread.sleep(500)
            print("HandlerThread: úloha dokončena")
        }

        // Zastavení vlákna (spustí se po dokončení úloh)
        handlerThread.quitSafely()
    }

    // 3. Executors — fond vláken
    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 — moderní standard
    suspend fun coroutineExample() = kotlinx.coroutines.withContext(
        kotlinx.coroutines.Dispatchers.Default
    ) {
        print("Coroutine on thread: ${Thread.currentThread().getName()}")
    }
}

Příklad ThreadExample ukazuje všechny čtyři úrovně abstrakce vláken v Androidu. Přímé vytvoření Thread je nejnízkoúrovňovější a nejméně efektivní přístup. HandlerThread je užitečný pro sekvenční úlohy na pozadí. Executors.newFixedThreadPool(4) vytváří fond 4 vláken pro paralelní provádění až 10 úloh. Kotlin Coroutines s Dispatchers.Default — moderní, efektivní a bezpečný způsob.

HandlerThread: sekvenční úlohy na pozadí

HandlerThread — specializovaná podtřída Thread s vestavěným Looper a frontou zpráv. Vytváří se voláním start(), poté lze přes Handler(handlerThread.looper) odesílat Runnable a zprávy. HandlerThread provádí úlohy přísně sekvenčně — další úloha nezačne, dokud se nedokončí předchozí. To je vhodné pro zápis dat do Room nebo souborů, kde je pořadí operací kritické. Volání quitSafely() zastaví Looper po dokončení aktuální úlohy.

Thread v iOS: Thread, GCD a OperationQueue

iOS také nabízí tři úrovně práce s vlákny. Thread (Thread ve Swift, NSThread v Objective-C) — nízkoúrovňové API, které přímo vytváří nativní vlákno. GCD (Grand Central Dispatch) přes DispatchQueue — hlavní nástroj pro vývojáře iOS, který automaticky spravuje fond vláken. OperationQueue — vysokoúrovňová abstrakce nad GCD s podporou závislostí, priorit a rušení.

Přímé použití Thread v moderním vývoji iOS je extrémně vzácné — GCD poskytuje všechny potřebné možnosti s automatickou správou paměti a vláken. Thread se používá pouze pro specifické případy: nastavení thread-local storage (threadDictionary), vytvoření RunLoop pro vlákno na pozadí nebo integrace s knihovnami C, které očekávají pthread_t.

swift
import Foundation

class ThreadManager {

    // 1. Thread (nízkoúrovňový)
    func createThread() {
        let thread = Thread {
            // Kód běží na novém vlákně
            print("Current thread: \(Thread.current)")
        }
        thread.name = "com.app.worker"
        thread.qualityOfService = .utility
        thread.start()
    }

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

        queue.async {
            print("Asynchronní úloha GCD")
        }

        // Barrier pro synchronizaci zápisu
        queue.async(flags: .barrier) {
            // Exkluzivní přístup během zápisu
            print("Barrier write: exkluzivní přístup")
        }
    }

    // 3. OperationQueue se závislostmi
    func operationQueueExample() {
        let queue = OperationQueue()
        queue.maxConcurrentOperationCount = 2
        queue.qualityOfService = .background

        let download = BlockOperation {
            print("Stahování...")
        }
        let process = BlockOperation {
            print("Zpracování...")
        }
        let save = BlockOperation {
            print("Ukládání...")
        }

        // Závislosti: download -> process -> save
        process.addDependency(download)
        save.addDependency(process)

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

// Thread-safe kolekce přes GCD barrier
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)
        }
    }
}

Třída ThreadSafeArray demonstruje vzor Concurrent Read / Exclusive Write přes GCD barrier. Čtení přes queue.sync{} probíhá paralelně z více vláken. Zápis přes queue.async(flags: .barrier) blokuje všechny ostatní operace (čtení i zápis) do dokončení zápisu. To je efektivnější než bloky synchronized, protože neblokuje čtenáře, dokud neprobíhá zápis.

iOS Thread vs GCD: kdy použít Thread přímo

Přímé použití Thread v iOS je opodstatněné ve třech případech: pro thread-local storage (Thread.current.threadDictionary) — ukládání dat vázaných na vlákno; pro vytvoření speciálního RunLoop na vlákně na pozadí pomocí performSelector:onThread:; pro integraci s knihovnami C/C++, které očekávají pthread_t. Ve všech ostatních případech je preferováno GCD přes DispatchQueue — automaticky spravuje fond vláken a spotřebu energie.

Synchronizace vláken: locks, atomic, serial queues

Race condition (soutěžní stav) vzniká, když dvě a více vláken současně přistupují ke sdíleným datům a alespoň jedno z nich provádí zápis. Výsledek závisí na pořadí provádění (timing) a je nepředvídatelný. K prevenci race condition se používají synchronizační primitiva. V mobilním vývoji jsou k dispozici zámky (synchronized, NSLock), atomické operace (AtomicInteger, vlastnosti atomic v iOS) a fronty (serial queue).

Výběr primitiva závisí na scénáři. Pro jednoduché počítadla a příznaky stačí atomické operace (AtomicInteger, atomic property). Pro kritické sekce s více operacemi — zámky (synchronized, NSLock). Pro složité datové struktury — serial DispatchQueue nebo GCD barrier. Zámky jsou srozumitelnější, ale jsou náchylné k deadlockům a livelockům. Fronty jsou složitější, ale bezpečnější.

kotlin
// Synchronizace v Android/Kotlin
import java.util.concurrent.atomic.AtomicInteger
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

class Counter {

    // 1. AtomicInteger — pro jednoduchá počítadla
    private val atomicCount = AtomicInteger(0)
    fun incrementAtomic() = atomicCount.incrementAndGet()

    // 2. synchronized — pro kritické sekce
    @Synchronized
    fun synchronizedOperation() {
        // Vždy jen jedno vlákno
        doWork()
    }

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

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

// Příklad deadlock: A blokuje B, B blokuje 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 demonstruje tři přístupy k synchronizaci. AtomicInteger.incrementAndGet() — atomická operace bez zámků (CAS). @Synchronized — vestavěný monitor Java, blokuje celý objekt. Mutex.withLock — korutinový mutex, pozastavuje korutinu místo blokování vlákna (efektivnější). DeadlockExample ukazuje klasický deadlock: dvě vlákna získávají zámky v různém pořadí.

Thread Pools: proč je Executors lepší než Thread

Thread Pool (fond vláken) — soubor předem vytvořených vláken, která se znovu používají pro provádění úloh. Místo vytváření nového vlákna pro každou úlohu (drahé) fond vezme volné vlákno z fondu. Pokud nejsou volná vlákna, úloha se zařadí do fronty. Fond automaticky spravuje velikost: nová vlákna se vytvářejí při špičkovém zatížení, neaktivní vlákna se ukončují. Tím se desetkrát snižuje režie vytváření vláken.

V Androidu Executors.newFixedThreadPool(4) vytváří fond 4 vláken. Pokud současně přijde 10 úloh, 4 začnou okamžitě a 6 bude čekat ve frontě. Executors.newCachedThreadPool() vytváří vlákna podle potřeby (bez limitu) a po 60 sekundách ukončuje neaktivní. Pro iOS GCD automaticky poskytuje fondy globálních front, jejichž velikost odpovídá počtu jader CPU a aktuálnímu zatížení.

V Kotlin Coroutines jsou fondy vláken skryté uvnitř dispatcherů. Dispatchers.Default používá fond velikosti rovnající se počtu jader CPU (minimálně 2). Dispatchers.IO — 64 vláken (dostatečné pro stovky úloh IO-bound, protože většina bude čekat na vstup-výstup, aniž by zabírala CPU). Každý dispatcher automaticky škáluje fond podle zatížení a šetří energii baterie v klidovém stavu.

Časté dotazy

Co je Thread v mobilním vývoji?

Thread — základní jednotka provádění kódu v aplikaci. Každý proces může mít mnoho vláken, která sdílejí paměť, ale mají vlastní zásobník. V mobilním vývoji se vlákna používají pro paralelní provádění úloh bez blokování UI. Android používá Thread, Executors, HandlerThread a Coroutines. iOS používá Thread, GCD (DispatchQueue) a OperationQueue.

Proč se nedoporučuje vytvářet Thread přímo?

Vytvoření Thread vyžaduje alokaci ~1 MB pro zásobník v Androidu a ~512 KB v iOS — to je nákladná operace. Pro 1000 úloh by přímé vytvoření 1000 vláken vyžadovalo ~1 GB jen na zásobníky plus režii na context switch. Místo Thread používejte fondy (Executors, GCD) nebo korutiny — znovu používají vlákna a desetkrát snižují režii.

Co je race condition a jak se jí vyhnout?

Race condition — nepředvídatelné chování při současném přístupu více vláken ke sdíleným datům se zápisem. Lze se jí vyhnout třemi způsoby: používat atomické typy (AtomicInteger), zámky (synchronized, NSLock) nebo serializovat přístup přes frontu (serial DispatchQueue, Actor v Kotlinu). Nejlepší praxe — minimalizovat sdílený mutable stav a používat immutability.

Čím se Thread liší od korutiny?

Thread — nativní systémový objekt, který zabírá ~1 MB zásobníku a je vázán na jádro operačního systému. Korutina — lehká jednotka provádění v Kotlinu, která není vázána na konkrétní vlákno a může být pozastavena (suspend) bez blokování. Jedno vlákno může provádět tisíce korutin. Korutiny jsou paměťově efektivnější a umožňují psát asynchronní kód bez callbacks.

Jak odhalit deadlock v mobilní aplikaci?

Deadlock se projevuje jako úplné zamrznutí aplikace bez ANR. V Androidu použijte Thread.getAllStackTraces() pro výpis zásobníků všech vláken — dvě vlákna budou čekat na zámky toho druhého. V iOS — Thread.callStackSymbols. Nástroje: Android Studio Profiler (karta Threads), Instruments (iOS, Thread State View). Prevence: zámky získávejte v pevném pořadí, používejte tryLock s časovým limitem.

Závěr

  • Thread — minimální jednotka CPU: nezávislé provádění s vlastním zásobníkem, paměť haldy je společná
  • Pět stavů vlákna: New, Runnable, Running, Blocked/Waiting, Terminated
  • Android se vyvinul od Thread → AsyncTask → Executors → HandlerThread → Coroutines
  • iOS nabízí Thread, GCD (DispatchQueue) a OperationQueue — od nízké po vysokou úroveň
  • Race condition se řeší zámky (synchronized, NSLock), atomickými typy a serial frontami
  • Deadlock vzniká při křížovém získání zámků — předchází se pevným pořadím
  • Thread Pool je efektivnější než vytváření nových Thread: znovu používá vlákna, snižuje režii na context switch

Vyvineme mobilní aplikaci na klíč

IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.

Prodiskutovat projekt

Přečtěte si také