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 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.
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.
| Parametr | Bez fondu (raw Thread) | S Thread Pool |
|---|---|---|
| Vytvoření vlákna | Pro každý úkol | Jednou při vytvoření fondu |
| Maximum vláken | Neomezené (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áva | Ruční (join, interrupt) | Automatická (ExecutorService) |
| Spotřeba paměti | Roste s každým úkolem | Fixní |
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 — 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.
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.
// 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)
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 (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.
// 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()
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.
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 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.
// 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()
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).
// 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()
}
}
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.
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.
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.
| Parametr | Doporučení pro mobil | Odůvodnění |
|---|---|---|
| corePoolSize | 2-4 | Omezené zdroje mobilního zařízení |
| maxPoolSize | corePoolSize (nebo +1-2) | Zabránění špičkovému zatížení při vytváření vláken |
| keepAliveTime | 15-30 sekund | Rychlé uvolnění paměti, ale bez častého vytváření |
| Queue capacity | 16-32 | Rovnováha mezi bufferováním a rizikem OOM |
| Handler | CallerRunsPolicy | Zpětný tlak bez ztráty úloh |
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 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().
// 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)
}
}
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
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).
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.
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.
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.
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
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í.
Přečtěte si také