Thread Pool a mobilfejlesztésben — alapok, szálkészlet és működési elv

Szerző: IT Sectr Megjelenés: 2026-03-18 Olvasási idő: 11 perc

Thread Pool — ez egy szálkezelő mechanizmus, amelyben egy előre létrehozott szálkészlet újra felhasználható feladatok végrehajtására, elkerülve a szálak létrehozásának és megsemmisítésének többletköltségeit. A mobilfejlesztésben a szálkészlet háttérműveletekhez használatos: hálózati kérések, képfeldolgozás, adatbázisokkal való munka. A Google Android Documentation (2025) szerint az ExecutorService az ajánlott módja a háttérszálak kezelésének Androidban. Az iOS-ben hasonló szerepet töltenek be az OperationQueue és a GCD DispatchQueue globális egyidejű sorokkal.

Főbb pontok

  • Thread Pool — újra felhasználható szálak készlete háttérfeladatok végrehajtásához, szálak létrehozásának többletköltsége nélkül.
  • ExecutorService Androidban a készletet ThreadPoolExecutor segítségével kezeli konfigurálható paraméterekkel.
  • OperationQueue iOS-ben a szálkészletet a maxConcurrentOperationCount segítségével inkapszulálja.
  • Core pool size — a szálak minimális száma, amelyek mindig készen állnak a feladatok végrehajtására.
  • Work queue tárolja a készletben szabad szálra váró feladatokat.

Mi az a Thread Pool?

Thread Pool (szálkészlet) — egy architekturális minta, amelyben egy rögzített számú szálat előre létrehoznak és újra felhasználnak több feladat végrehajtásához. Ahelyett, hogy minden művelethez új szálat hoznának létre (ami költséges: körülbelül 1 MB verem szálanként a JVM-ben), a feladatokat egy sorba helyezik, és a készletből szabad szálak hajtják végre. A mobilfejlesztésben a szálkészlet kritikus fontosságú a teljesítmény szempontjából — az Android és az iOS korlátozza az alkalmazásonkénti szálak számát.

Miért fontos a Thread Pool a mobilfejlesztésben

A szál létrehozása költséges művelet: veremfoglalás, rendszerbe regisztrálás, kontextusváltás. A korlátozott erőforrásokkal rendelkező mobileszközökön a szálak ellenőrizetlen létrehozása OOM-hoz (OutOfMemoryError) vezet Androidban és throttlinghoz iOS-ben. Thread Pool mindkét problémát megoldja: korlátozza az egyidejűleg futó szálak maximális számát és újra felhasználja a már létrehozott szálakat. A Google az ExecutorService-t ajánlja a raw Thread() helyett, az Apple az OperationQueue-t ajánlja a Thread helyett.

ParaméterKészlet nélkül (raw Thread)Thread Pool-lal
Szál létrehozásaMinden feladathozEgyszer a készlet létrehozásakor
Maximum szálakKorlátlan (OOM kockázat)Core/max pool size által korlátozva
KihasználtságAlacsony (a szál meghal a feladat után)Magas (a szál újra felhasználható)
KezelésKézi (join, interrupt)Automatikus (ExecutorService)
MemóriafogyasztásMinden feladattal nőRögzített

Hogyan működik a Thread Pool a mobilfejlesztésben?

A szálkészlet a Producer-Consumer elv alapján működik: a feladatokat (Runnable/Callable) egy blokkoló sorba (BlockingQueue) helyezik. A készletből származó szálak a sorban várják a feladatokat, és elviszik azokat végrehajtásra. Algoritmus: ha kevesebb a szabad szál, mint a corePoolSize, új szál jön létre. Ha a corePoolSize elérte, a feladat a sorba kerül. Ha a sor megtelt és kevesebb a szál, mint a maximumPoolSize, egy további szál jön létre. A maximumPoolSize túllépésekor a feladat elutasításra kerül a RejectedExecutionHandler segítségével.

Core Pool Size vs Maximum Pool Size

Core pool size — a készletben még tétlen állapotban is fenntartott szálak száma. Maximum pool size — a sor túlcsordulásakor létrehozható szálak maximális száma. A köztük lévő különbség a további (overflow) szálak, amelyek ideiglenesen jönnek létre és az idle időtúllépés után befejeződnek. Mobileszközökön ajánlott a corePoolSize-t a maximumPoolSize-szal egyenlőre állítani a szálak létrehozásának csúcsterhelésének elkerülése érdekében.

Work Queue és RejectedExecutionHandler

BlockingQueue tárolja a végrehajtásra váró feladatokat. A legnépszerűbb implementációk: LinkedBlockingQueue (korlátlan), ArrayBlockingQueue (korlátozott) és SynchronousQueue (tárolás nélkül — a feladat azonnal átadásra kerül a szálnak). A sor és a készlet túlcsordulásakor a RejectedExecutionHandler aktiválódik. Szabványos házirendek: AbortPolicy (RejectedExecutionException-t dob), CallerRunsPolicy (a feladó szálában hajtja végre), DiscardPolicy és DiscardOldestPolicy.

kotlin
// Thread Pool létrehozása Androidban
val threadPool = ThreadPoolExecutor(
    corePoolSize = 2,        // Minimum 2 szál
    maximumPoolSize = 4,     // Maximum 4 szál
    keepAliveTime = 30L,     // Túlcsordulási szál élettartama
    unit = TimeUnit.SECONDS,
    workQueue = LinkedBlockingQueue<Runnable>(16),
    threadFactory = Executors.defaultThreadFactory(),
    handler = ThreadPoolExecutor.CallerRunsPolicy()
)

// Feladatok elküldése
threadPool.execute {
    val result = api.fetchData()
    runOnUiThread { showData(result) }
}

// Készlet lezárása
threadPool.shutdown()
// Az összes feladat befejezésének várása
threadPool.awaitTermination(10, TimeUnit.SECONDS)

Thread Pool Androidban: ExecutorService

Az Android több szálkészlet-implementációt kínál a java.util.concurrent segítségével. Executors — egy gyár kész konfigurációkkal: newFixedThreadPool(n) (rögzített készlet), newCachedThreadPool() (korlátlan, szálak szükség szerint jönnek létre), newSingleThreadExecutor() (egy szál — szekvenciális végrehajtás). Mobil projektekhez a newFixedThreadPool ésszerű korláttal (2-4 szál) ajánlott, mivel a cached pool túl sok szálat hozhat létre.

ThreadPoolExecutor Androidban

ThreadPoolExecutor (TPE) — az ExecutorService teljes implementációja konfigurálható paraméterekkel. Androidban a TPE az AsyncTask, IntentService és JobIntentService belső működésében használatos. A corePoolSize, maximumPoolSize, keepAliveTime, BlockingQueue és RejectedExecutionHandler paraméterek lehetővé teszik a készlet viselkedésének finomhangolását. Ajánlások Androidhoz: corePoolSize = CPU magok száma - 1 (IO-bound feladatokhoz) vagy magok száma (CPU-bound feladatokhoz). Tipikus alkalmazásokhoz — 2-4 szál.

kotlin
// Kész Executors konfigurációk
// 1. Fix készlet 3 szálra
val fixedPool = Executors.newFixedThreadPool(3)

// 2. Gyorsítótárazott készlet (nem ajánlott mobilra)
val cachedPool = Executors.newCachedThreadPool()

// 3. Egyetlen szál (szerializáció)
val singlePool = Executors.newSingleThreadExecutor()

// 4. Ütemező (periodikus feladatok)
val scheduler = Executors.newScheduledThreadPool(2)

// Használat Callable és Future segítségével
val future: Future<String> = fixedPool.submit(Callable {
    "Result: ${api.call()}"
})
// Eredmény lekérése (blokkolja a szálat)
val result = future.get(5, TimeUnit.SECONDS)

// Készlet lezárása
fixedPool.shutdownNow()

CoroutineDispatcher szálkészletként

Kotlin korutinok CoroutineDispatcher-t biztosítanak — a szálkészlethez hasonló absztrakciót. A Dispatchers.IO egy 64 szálból álló készletet (korlátozott) használ. A Dispatchers.Default — a CPU magok számával megegyező készlet. A CoroutineDispatcher nem igényel kézi shutdown-t és automatikusan kezelődik. Finomhangoláshoz hozza létre saját ExecutorCoroutineDispatcher-jét az Executors.newFixedThreadPool(2).asCoroutineDispatcher() segítségével. A korutinok nem helyettesítik a szálkészletet, hanem beburkolják azt.

Thread Pool iOS-ben: OperationQueue és GCD

Az iOS két fő mechanizmust kínál a szálkészlet kezelésére: OperationQueue (GCD-alapú magas szintű API) és GCD DispatchQueue (alacsony szintű C-API). Az OperationQueue a szálkészletet a maxConcurrentOperationCount tulajdonság segítségével inkapszulálja. Alapértelmezés szerint az OperationQueue a system-defined maximum-ot használja (a rendszer terhelésétől függően). A DispatchQueue.global() egy egyidejű sort biztosít a rendszer szálkészletével.

OperationQueue és maxConcurrentOperationCount

OperationQueue a szálkészletet a maxConcurrentOperationCount segítségével kezeli. Az 1-es érték soros sort hoz létre (hasonlóan a single thread pool-hoz). Az 1-nél nagyobb érték — egyidejű készlet a megadott korláttal. Alapértelmezés szerint maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount (rendszer optimum, általában 4-8 szál). Az Operation támogatja a függőségeket, prioritásokat és megszakítást. Minden művelet a rendszerkészlet bármely szabad szálán végrehajtódik.

swift
// OperationQueue 3 szálas készlettel
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 3
queue.qualityOfService = .utility

// Műveletek létrehozása
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) }
}

// Függőség: operation2 vár operation1-re
operation2.addDependency(operation1)

// Hozzáadás a sorhoz
queue.addOperations([operation1, operation2], waitUntilFinished: false)

// Összes művelet megszakítása
queue.cancelAllOperations()

GCD DispatchQueue szálkészletként

DispatchQueue — az Apple szálkészlete. Az egyidejű sor (qos: .utility) a rendszer szálkészletét használja, amely az eszköz aktuális terheléséhez van optimalizálva. A különböző QoS-ek (userInteractive, userInitiated, utility, background) különböző prioritású készletekre vannak leképezve. DispatchGroup lehetővé teszi több feladat szinkronizálását. A DispatchWorkItem támogatja a megszakítást és a qualityOfService-t. Finom vezérléshez hozza létre saját egyidejű sorait a DispatchQueue(label: qos: attributes: .concurrent) segítségével.

swift
// GCD DispatchQueue szálkészletként
let customQueue = DispatchQueue(
    label: "com.app.background",
    qos: .utility,
    attributes: .concurrent,
    autoreleaseFrequency: .workItem
)

// Feladatok küldése a készletbe
customQueue.async { self.processFile(file1) }
customQueue.async { self.processFile(file2) }

// DispatchGroup a szinkronizációhoz
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() // Mindkét feladat befejeződött
}

// Egyidejűség korlátozása semaphore segítségével
let semaphore = DispatchSemaphore(value: 3)
for url in urls {
    pool.async {
        semaphore.wait()
        download(url)
        semaphore.signal()
    }
}

Thread Pool konfigurációs paraméterei

A szálkészlet konfigurációja közvetlenül befolyásolja az alkalmazás teljesítményét. Helytelen paraméterek a CPU alulhasználtságához (túl kevés szál) vagy a rendszer túlterheléséhez (túl sok szál) vezetnek. Mobil alkalmazásokban az optimális értékek eltérnek a szerver értékektől a korlátozott erőforrások és energiafogyasztás miatt. Fő paraméterek: corePoolSize, maxPoolSize, queue capacity és keepAliveTime.

Az optimális készletméret kiszámítása

Képlet IO-bound feladatokhoz: corePoolSize = CPU magok száma × 2 (a szálak bemenet-kimenetre várnak). CPU-bound feladatokhoz: corePoolSize = CPU magok száma (a szálak folyamatosan számításokkal vannak elfoglalva). Modern mobileszközökön (6-8 mag) ez 6-8 szálat ad CPU-bound és 12-16-ot IO-bound esetén. Gyakorlati tesztek azt mutatják, hogy egy tipikus mobil alkalmazáshoz 3-4 szál optimális — több szál növeli az energiafogyasztást a teljesítmény növelése nélkül.

Queue Capacity és viselkedés túlcsorduláskor

A feladatsor (work queue) mérete határozza meg, hogy hány feladat várhat végrehajtásra. Korlátlan sor (LinkedBlockingQueue korlát nélkül) OOM-hoz vezethet a feladatok gyors érkezésekor. Korlátozott sor (ArrayBlockingQueue rögzített mérettel) elutasítja a feladatokat túlcsorduláskor. Mobil alkalmazásokhoz 16-32 feladat kapacitású ArrayBlockingQueue ajánlott. CallerRunsPolicy — a legjobb RejectedExecutionHandler mobileszközökhöz: lelassítja a feladót (pressure back) a feladat elvesztése helyett.

ParaméterAjánlás mobilhozIndoklás
corePoolSize2-4Mobileszköz korlátozott erőforrásai
maxPoolSizecorePoolSize (vagy +1-2)Szálak létrehozásának csúcsterhelésének elkerülése
keepAliveTime15-30 másodpercGyors memóriafelszabadítás, de gyakori létrehozás nélkül
Queue capacity16-32Egyensúly a pufferelés és az OOM kockázat között
HandlerCallerRunsPolicyVisszanyomás feladatvesztés nélkül

Gyakori hibák a szálkészlettel való munka során

A mobil alkalmazásfejlesztők gyakran követnek el hibákat a szálkészlet használata során, amelyek összeomlásokhoz, memóriaszivárgásokhoz és instabil működéshez vezetnek. Leggyakoribbak: a shutdown() nem hívása ExecutorService esetén, új készlet létrehozása minden művelethez, túl nagy készlet, deadlock a feladatok között, CachedThreadPool használata Androidban.

Deadlock a Thread Poolban

Deadlock akkor fordul elő, amikor egy készletben lévő feladat egy másik feladat eredményére vár ugyanabból a készletből, de minden szál a várakozással van elfoglalva. Példa: az A feladat elküldi a B feladatot ugyanabba a készletbe és meghívja a future.get()-et — ha a készlet kimerült, az A feladat vár a B feladatra, a B feladat pedig nem tud végrehajtódni, mert nincs szabad szál. Megoldás: használjon külön készleteket a feladatok különböző szintjeihez vagy aszinkron callback-et a blokkoló .get() helyett.

kotlin
// Deadlock a Thread Poolban
val pool = Executors.newFixedThreadPool(1)

// A feladat vár B feladatra — deadlock!
val futureA = pool.submit {
    // Ez a feladat soha nem fog végrehajtódni
    val futureB = pool.submit { 42 }
    futureB.get() // Örökké blokkolva
}

// Javítás: külön készletek
val workerPool = Executors.newFixedThreadPool(2)
val callbackPool = Executors.newSingleThreadExecutor()

workerPool.submit {
    callbackPool.submit {
        // Külön készletben hajtódik végre — deadlock lehetetlen
    }
}

// Vagy használjon CompletableFuture-t
workerPool.submit {
    CompletableFuture
        .supplyAsync { 42 }
        .thenAccept { result ->
            println(result)
        }
}

Befejezetlen készlet és szivárgások

Az Activity-ben létrehozott ExecutorService-t be kell fejezni az onDestroy()-ben. Ha ez nem történik meg, a szálak a memóriában maradnak az Activity megsemmisítése után is. Megoldás: tárolja a készletet Application scope-ban vagy ViewModel-ben, ne az Activity-ben. Korutinokhoz használjon viewModelScope-t vagy lifecycleScope-ot. Ha a készlet az Activity-n belül lett létrehozva, feltétlenül hívja meg a pool.shutdown()-ot az onDestroy()-ben. Teszteléshez használja az es.shutdownNow()-t az azonnali leállításhoz.

Gyakran ismételt kérdések

Miben különbözik a Thread Pool a hétköznapi száltól?

Thread Pool újra felhasználja a már létrehozott szálakat több feladat végrehajtásához. Egy hétköznapi szál (raw Thread) létrejön, végrehajt egy feladatot és megsemmisül. Egy szál létrehozása körülbelül 1 MB memóriát és ~1 ms időt vesz igénybe. A Thread Pool csökkenti a többletköltségeket, korlátozza a szálak maximális számát és kezelési API-t (shutdown, awaitTermination) biztosít.

Hány szálnak kell lennie a készletben egy mobil alkalmazáshoz?

Egy tipikus mobil alkalmazáshoz 2-4 szál optimális. CPU-bound feladatokhoz — a CPU magok száma. IO-bound feladatokhoz — a magok száma × 2. Több szál növeli az energiafogyasztást és a kontextusváltást a teljesítmény növelése nélkül. Androidban a Process.availableProcessors() segítségével határozza meg a magokat. iOS-ben — a ProcessInfo.processInfo.processorCount segítségével.

Mi az a CachedThreadPool és miért veszélyes Androidban?

CachedThreadPool szükség szerint hoz létre szálakat és újra felhasználja a meglévőket. Probléma: nem korlátozza a szálak maximális számát. Ha 100 feladat érkezik egyszerre, 100 szál jön létre. Ez OOM-hoz vezet Androidban (minden szál ~1 MB). Használjon newFixedThreadPool(n)-t explicit korláttal. A CachedThreadPool csak rövid idejű burst feladatokhoz engedélyezett kis mennyiség garanciájával.

Meg kell hívni a shutdown()-ot az ExecutorService számára?

Igen, ha a készlet nem tartozik egy felügyelt tárolóhoz (mint a korutinok). A shutdown() leállítja az új feladatok fogadását és befejezi a szálakat az aktuális feladatok befejezése után. A shutdown() nélkül a szálak a memóriában maradnak, az alkalmazás nem fejeződik be. Activity esetén hívja az onDestroy()-ben. ViewModel esetén használjon coroutineScope-ot. A készlet befejezése az erőforrás-kezelés kötelező része, hasonlóan a Cursor vagy InputStream bezárásához.

Az OperationQueue és a DispatchQueue szálkészlet?

Igen, az OperationQueue és a DispatchQueue az iOS által biztosított szálkészlet. Az OperationQueue a maxConcurrentOperationCount segítségével korlátozza az egyidejűséget. A DispatchQueue.global() a rendszer szálkészletét használja közvetlen vezérlés nélkül. A Java ThreadPoolExecutorral ellentétben nem kezeli a corePoolSize-t vagy a queue capacity-t — a rendszer automatikusan optimalizálja a készletet az aktuális terhelés és az eszköz energiafogyasztása alapján.

Összefoglalás

  • Thread Pool — újra felhasználható szálak készlete háttérfeladatok végrehajtásához, csökkentve a szálak létrehozásának többletköltségét.
  • Android ThreadPoolExecutor-t és Executors.newFixedThreadPool(n)-t használ explicit készletméret-korlátozással.
  • iOS OperationQueue-t biztosít maxConcurrentOperationCount-val és GCD DispatchQueue-t QoS készletekkel.
  • Core pool size — a szálak minimális száma; maximum pool size — a maximum a sor túlcsordulásakor.
  • Deadlock a készletben akkor fordul elő, amikor egy feladat egy másik feladatra vár ugyanabból a készletből.
  • CallerRunsPolicy előnyösebb mobil alkalmazásokhoz — lelassítja a feladót a feladat elvesztése nélkül.
  • Az optimális készletméret egy tipikus mobil alkalmazáshoz 2-4 szál a shutdown()-on keresztüli lezárással.

Kulcsrakész mobilalkalmazást fejlesztünk

Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.

Projekt megbeszélése

Olvassa el is