Thread Pool v mobilním vývoji — základy, fond vláken a princip činnosti

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

Thread Pool — je mechanismus správy vláken, při kterém se předem vytvořený fond vláken znovu používá k provádění úloh, čímž se zabrání režii spojené s vytvářením a ničením vláken. V mobilním vývoji se fond vláken používá pro operace na pozadí: síťové požadavky, zpracování obrázků, práce s databázemi. Podle Google Android Documentation (2025) je ExecutorService doporučeným způsobem správy vláken na pozadí v Android. V iOS hrají podobnou roli OperationQueue a GCD DispatchQueue s globálními souběžnými frontami.

Hlavní body

  • Thread Pool — fond znovu použitelných vláken pro provádění úloh na pozadí bez režie vytváření vláken.
  • ExecutorService v Android spravuje fond pomocí ThreadPoolExecutor s konfigurovatelnými parametry.
  • OperationQueue v iOS zapouzdřuje fond vláken pomocí maxConcurrentOperationCount.
  • Core pool size — minimální počet vláken vždy připravených k provádění úloh.
  • Work queue ukládá úlohy čekající na volné vlákno ve fondu.

Co je Thread Pool?

Thread Pool (fond vláken) — je architektonický vzor, při kterém je pevný počet vláken vytvořen předem a znovu používán k provádění více úloh. Místo vytváření nového vlákna pro každou operaci (což je nákladné: přibližně 1 MB zásobníku na vlákno v JVM) jsou úlohy umístěny do fronty a prováděny volnými vlákny z fondu. V mobilním vývoji je fond vláken kritický pro výkon — Android a iOS omezují počet vláken na aplikaci.

Proč je Thread Pool důležitý v mobilním vývoji

Vytvoření vlákna je nákladná operace: alokace zásobníku, registrace v systému, přepínání kontextu. Na mobilních zařízeních s omezenými zdroji vede nekontrolované vytváření vláken k OOM (OutOfMemoryError) v Android a k throttlingu v iOS. Thread Pool řeší oba problémy: omezuje maximální počet současně běžících vláken a znovu využívá již vytvořená vlákna. Google doporučuje ExecutorService místo raw Thread(), Apple doporučuje OperationQueue místo Thread.

ParametrBez fondu (raw Thread)S Thread Pool
Vytvoření vláknaPro každý úkolJednou při vytvoření fondu
Maximum vlákenNeomezené (riziko OOM)Omezeno core/max pool size
VyužitíNízké (vlákno zemře po úkolu)Vysoké (vlákno je znovu použito)
SprávaRuční (join, interrupt)Automatická (ExecutorService)
Spotřeba pamětiRoste s každým úkolemFixní

Jak funguje Thread Pool v mobilním vývoji?

Fond vláken pracuje na principu Producer-Consumer: úlohy (Runnable/Callable) jsou umístěny do blokující fronty (BlockingQueue). Vlákna z fondu čekají na úlohy ve frontě a odebírají je k provedení. Algoritmus: pokud je volných vláken méně než corePoolSize, vytvoří se nové vlákno. Pokud je corePoolSize dosaženo, úloha je umístěna do fronty. Pokud je fronta plná a vláken je méně než maximumPoolSize, vytvoří se další vlákno. Při překročení maximumPoolSize je úloha odmítnuta prostřednictvím RejectedExecutionHandler.

Core Pool Size vs Maximum Pool Size

Core pool size — počet vláken udržovaných ve fondu i v nečinnosti. Maximum pool size — maximální počet vláken, která lze vytvořit při přetečení fronty. Rozdíl mezi nimi jsou další (overflow) vlákna, která jsou vytvořena dočasně a ukončena po uplynutí časového limitu nečinnosti. Na mobilních zařízeních se doporučuje nastavit corePoolSize rovný maximumPoolSize, aby se zabránilo špičkovému zatížení při vytváření vláken.

Work Queue a RejectedExecutionHandler

BlockingQueue ukládá úlohy čekající na provedení. Nejoblíbenější implementace: LinkedBlockingQueue (neomezená), ArrayBlockingQueue (omezená) a SynchronousQueue (bez ukládání — úloha je okamžitě předána vláknu). Při přetečení fronty a fondu se aktivuje RejectedExecutionHandler. Standardní politiky: AbortPolicy (vyhazuje RejectedExecutionException), CallerRunsPolicy (provádí ve vlákně odesílatele), DiscardPolicy a DiscardOldestPolicy.

kotlin
// Vytvoření Thread Pool v Android
val threadPool = ThreadPoolExecutor(
    corePoolSize = 2,        // Minimálně 2 vlákna
    maximumPoolSize = 4,     // Maximálně 4 vlákna
    keepAliveTime = 30L,     // Životnost vlákna při přetečení
    unit = TimeUnit.SECONDS,
    workQueue = LinkedBlockingQueue<Runnable>(16),
    threadFactory = Executors.defaultThreadFactory(),
    handler = ThreadPoolExecutor.CallerRunsPolicy()
)

// Odesílání úloh
threadPool.execute {
    val result = api.fetchData()
    runOnUiThread { showData(result) }
}

// Ukončení fondu
threadPool.shutdown()
// Čekání na dokončení všech úloh
threadPool.awaitTermination(10, TimeUnit.SECONDS)

Thread Pool v Android: ExecutorService

Android poskytuje několik implementací fondu vláken prostřednictvím java.util.concurrent. Executors — továrna s hotovými konfiguracemi: newFixedThreadPool(n) (pevný fond), newCachedThreadPool() (neomezený, vlákna se vytvářejí podle potřeby), newSingleThreadExecutor() (jedno vlákno — sekvenční provádění). Pro mobilní projekty se doporučuje newFixedThreadPool s rozumným limitem (2-4 vlákna), protože cached fond může vytvořit příliš mnoho vláken.

ThreadPoolExecutor v Android

ThreadPoolExecutor (TPE) — plná implementace ExecutorService s konfigurovatelnými parametry. V Android se TPE používá uvnitř AsyncTask, IntentService a JobIntentService. Parametry corePoolSize, maximumPoolSize, keepAliveTime, BlockingQueue a RejectedExecutionHandler umožňují jemné doladění chování fondu. Doporučení pro Android: corePoolSize = počet jader CPU - 1 (pro úlohy IO-bound) nebo počet jader (pro úlohy CPU-bound). Pro typické aplikace — 2-4 vlákna.

kotlin
// Hotové konfigurace Executors
// 1. Pevný fond pro 3 vlákna
val fixedPool = Executors.newFixedThreadPool(3)

// 2. Cache fond (nedoporučuje se pro mobil)
val cachedPool = Executors.newCachedThreadPool()

// 3. Jedno vlákno (serializace)
val singlePool = Executors.newSingleThreadExecutor()

// 4. Plánovač (periodické úlohy)
val scheduler = Executors.newScheduledThreadPool(2)

// Použití s Callable a Future
val future: Future<String> = fixedPool.submit(Callable {
    "Result: ${api.call()}"
})
// Získání výsledku (blokuje vlákno)
val result = future.get(5, TimeUnit.SECONDS)

// Ukončení fondu
fixedPool.shutdownNow()

CoroutineDispatcher jako fond vláken

Kotlin korutiny poskytují CoroutineDispatcher — abstrakci podobnou fondu vláken. Dispatchers.IO používá fond 64 vláken (omezený). Dispatchers.Default — fond rovný počtu jader CPU. CoroutineDispatcher nevyžaduje ruční shutdown a je spravován automaticky. Pro jemné doladění vytvořte vlastní ExecutorCoroutineDispatcher pomocí Executors.newFixedThreadPool(2).asCoroutineDispatcher(). Korutiny nenahrazují fond vláken, ale obalují jej.

Thread Pool v iOS: OperationQueue a GCD

iOS poskytuje dva hlavní mechanismy pro správu fondu vláken: OperationQueue (high-level API založené na GCD) a GCD DispatchQueue (low-level C-API). OperationQueue zapouzdřuje fond vláken pomocí vlastnosti maxConcurrentOperationCount. Ve výchozím nastavení OperationQueue používá system-defined maximum (závislé na zatížení systému). DispatchQueue.global() poskytuje souběžnou frontu se systémovým fondem vláken.

OperationQueue a maxConcurrentOperationCount

OperationQueue spravuje fond vláken pomocí maxConcurrentOperationCount. Hodnota 1 vytváří sériovou frontu (podobně jako single thread pool). Hodnota větší než 1 — souběžný fond s uvedeným limitem. Ve výchozím nastavení maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount (systémové optimum, obvykle 4-8 vláken). Operation podporuje závislosti, priority a zrušení. Každá operace je provedena na libovolném volném vlákně ze systémového fondu.

swift
// OperationQueue s fondem 3 vláken
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 3
queue.qualityOfService = .utility

// Vytváření operací
let operation1 = BlockOperation {
    let data = fetchData(from: url1)
    DispatchQueue.main.async { updateUI(data) }
}

let operation2 = BlockOperation {
    let data = fetchData(from: url2)
    DispatchQueue.main.async { updateUI(data) }
}

// Závislost: operation2 čeká na operation1
operation2.addDependency(operation1)

// Přidání do fronty
queue.addOperations([operation1, operation2], waitUntilFinished: false)

// Zrušení všech operací
queue.cancelAllOperations()

GCD DispatchQueue jako fond vláken

DispatchQueue — fond vláken od Apple. Souběžná fronta (qos: .utility) používá systémový fond vláken optimalizovaný pro aktuální zatížení zařízení. Různé QoS (userInteractive, userInitiated, utility, background) se mapují na různé fondy s různými prioritami. DispatchGroup umožňuje synchronizaci více úloh. DispatchWorkItem podporuje zrušení a qualityOfService. Pro jemnou kontrolu vytvářejte vlastní souběžné fronty pomocí DispatchQueue(label: qos: attributes: .concurrent).

swift
// GCD DispatchQueue jako fond vláken
let customQueue = DispatchQueue(
    label: "com.app.background",
    qos: .utility,
    attributes: .concurrent,
    autoreleaseFrequency: .workItem
)

// Odesílání úloh do fondu
customQueue.async { self.processFile(file1) }
customQueue.async { self.processFile(file2) }

// DispatchGroup pro synchronizaci
let group = DispatchGroup()
let pool = DispatchQueue.global(qos: .utility)

pool.async(group: group) { fetchData() }
pool.async(group: group) { processImage() }

group.notify(queue: .main) {
    self.showResult() // Obě úlohy dokončeny
}

// Omezení souběžnosti pomocí semaphore
let semaphore = DispatchSemaphore(value: 3)
for url in urls {
    pool.async {
        semaphore.wait()
        download(url)
        semaphore.signal()
    }
}

Parametry konfigurace Thread Pool

Konfigurace fondu vláken přímo ovlivňuje výkon aplikace. Nesprávné parametry vedou k nedostatečnému využití CPU (příliš málo vláken) nebo přetížení systému (příliš mnoho). V mobilních aplikacích se optimální hodnoty liší od serverových kvůli omezeným zdrojům a spotřebě energie. Hlavní parametry: corePoolSize, maxPoolSize, queue capacity a keepAliveTime.

Výpočet optimální velikosti fondu

Vzorec pro úlohy IO-bound: corePoolSize = počet jader CPU × 2 (vlákna čekají na vstup-výstup). Pro úlohy CPU-bound: corePoolSize = počet jader CPU (vlákna jsou neustále zatížena výpočty). Na moderních mobilních zařízeních (6-8 jader) to dává 6-8 vláken pro CPU-bound a 12-16 pro IO-bound. Praktické testy ukazují, že pro typickou mobilní aplikaci jsou 3-4 vlákna optimální — více vláken zvyšuje spotřebu energie bez zvýšení výkonu.

Queue Capacity a chování při přetečení

Velikost fronty úloh (work queue) určuje, kolik úloh může čekat na provedení. Neomezená fronta (LinkedBlockingQueue bez limitu) může vést k OOM při rychlém příchodu úloh. Omezená fronta (ArrayBlockingQueue s pevnou velikostí) odmítá úlohy při přetečení. Pro mobilní aplikace se doporučuje ArrayBlockingQueue s kapacitou 16-32 úloh. CallerRunsPolicy — nejlepší RejectedExecutionHandler pro mobilní zařízení: zpomaluje odesílatele (pressure back) místo ztráty úlohy.

ParametrDoporučení pro mobilOdůvodnění
corePoolSize2-4Omezené zdroje mobilního zařízení
maxPoolSizecorePoolSize (nebo +1-2)Zabránění špičkovému zatížení při vytváření vláken
keepAliveTime15-30 sekundRychlé uvolnění paměti, ale bez častého vytváření
Queue capacity16-32Rovnováha mezi bufferováním a rizikem OOM
HandlerCallerRunsPolicyZpětný tlak bez ztráty úloh

Typické chyby při práci s fondem vláken

Vývojáři mobilních aplikací často dělají chyby při používání fondu vláken, které vedou k pádům, únikům paměti a nestabilnímu provozu. Nejčastější: nevolání shutdown() pro ExecutorService, vytváření nového fondu pro každou operaci, příliš velký fond, deadlock mezi úlohami, používání CachedThreadPool v Android.

Deadlock v Thread Pool

Deadlock nastává, když úloha ve fondu čeká na výsledek jiné úlohy ze stejného fondu, ale všechna vlákna jsou zaneprázdněna čekáním. Příklad: úloha A odešle úlohu B do stejného fondu a zavolá future.get() — pokud je fond vyčerpán, úloha A čeká na úlohu B a úloha B nemůže být provedena, protože nejsou volná vlákna. Řešení: použijte samostatné fondy pro různé úrovně úloh nebo asynchronní callback místo blokujícího .get().

kotlin
// Deadlock v Thread Pool
val pool = Executors.newFixedThreadPool(1)

// Úloha A čeká na úlohu B — deadlock!
val futureA = pool.submit {
    // Tato úloha se nikdy neprovede
    val futureB = pool.submit { 42 }
    futureB.get() // Navždy zablokováno
}

// Oprava: samostatné fondy
val workerPool = Executors.newFixedThreadPool(2)
val callbackPool = Executors.newSingleThreadExecutor()

workerPool.submit {
    callbackPool.submit {
        // Provádí se v samostatném fondu — deadlock nemožný
    }
}

// Nebo použijte CompletableFuture
workerPool.submit {
    CompletableFuture
        .supplyAsync { 42 }
        .thenAccept { result ->
            println(result)
        }
}

Nedokončený fond a úniky

ExecutorService vytvořený v Activity musí být ukončen v onDestroy(). Pokud se tak nestane, vlákna zůstanou v paměti i po zničení Activity. Řešení: uchovávejte fond v rozsahu Application nebo ViewModel, ne v Activity. Pro korutiny použijte viewModelScope nebo lifecycleScope. Pokud byl fond vytvořen uvnitř Activity, nezapomeňte zavolat pool.shutdown() v onDestroy(). Pro testování použijte es.shutdownNow() pro okamžité zastavení.

Často kladené otázky

Čím se Thread Pool liší od běžného vlákna?

Thread Pool znovu využívá již vytvořená vlákna k provádění více úloh. Běžné vlákno (raw Thread) je vytvořeno, provede jednu úlohu a je zničeno. Vytvoření vlákna zabere přibližně 1 MB paměti a ~1 ms času. Thread Pool snižuje režii, omezuje maximální počet vláken a poskytuje API pro správu (shutdown, awaitTermination).

Kolik vláken by mělo být ve fondu pro mobilní aplikaci?

Pro typickou mobilní aplikaci je optimálních 2-4 vláken. Pro úlohy CPU-bound — počet jader CPU. Pro úlohy IO-bound — počet jader × 2. Více vláken zvyšuje spotřebu energie a přepínání kontextu bez zvýšení výkonu. V Android použijte Process.availableProcessors() k určení jader. V iOS — ProcessInfo.processInfo.processorCount.

Co je CachedThreadPool a proč je nebezpečný v Android?

CachedThreadPool vytváří vlákna podle potřeby a znovu využívá stávající vlákna. Problém: neomezuje maximální počet vláken. Pokud 100 úloh přijde současně, bude vytvořeno 100 vláken. To vede k OOM v Android (každé vlákno ~1 MB). Používejte newFixedThreadPool(n) s explicitním limitem. CachedThreadPool je povolen pouze pro krátkodobé burst úlohy se zárukou malého objemu.

Je nutné volat shutdown() pro ExecutorService?

Ano, pokud fond nepatří do spravovaného kontejneru (jako korutiny). shutdown() zastavuje přijímání nových úloh a ukončuje vlákna po dokončení stávajících úloh. Bez shutdown() vlákna zůstávají v paměti a aplikace se neukončí. Pro Activity volejte v onDestroy(). Pro ViewModel použijte coroutineScope. Ukončení fondu je povinnou součástí správy zdrojů, obdobně jako zavírání Cursor nebo InputStream.

Jsou OperationQueue a DispatchQueue fond vláken?

Ano, OperationQueue a DispatchQueue jsou fond vláken poskytovaný iOS. OperationQueue omezuje souběžnost pomocí maxConcurrentOperationCount. DispatchQueue.global() používá systémový fond vláken bez přímé kontroly. Na rozdíl od Java ThreadPoolExecutor nespravujete corePoolSize ani queue capacity — systém optimalizuje fond automaticky podle aktuálního zatížení a spotřeby energie zařízení.

Souhrn

  • Thread Pool — fond znovu použitelných vláken pro provádění úloh na pozadí, snižující režii vytváření vláken.
  • Android používá ThreadPoolExecutor a Executors.newFixedThreadPool(n) s explicitním omezením velikosti fondu.
  • iOS poskytuje OperationQueue s maxConcurrentOperationCount a GCD DispatchQueue s QoS fondy.
  • Core pool size — minimální počet vláken; maximum pool size — maximum při přetečení fronty.
  • Deadlock ve fondu nastává, když úloha čeká na jinou úlohu ze stejného fondu.
  • CallerRunsPolicy je preferován pro mobilní aplikace — zpomaluje odesílatele bez ztráty úloh.
  • Optimální velikost fondu pro typickou mobilní aplikaci je 2-4 vlákna s uzavřením pomocí shutdown().

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é