Thread a mobilfejlesztésben — mi ez, típusai és a szálak kezelése

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

Thread — a processzoridő alapegysége, amelynek saját verme van, és más szálaktól függetlenül fut. A mobilfejlesztésben a szálakat a feladatok párhuzamos végrehajtására használják, hogy a felület hosszú műveletek során is reszponzív maradjon. Az Android támogatja a java.lang.Thread-et, az Executors-t és a Kotlin Coroutines-t, az iOS — a Thread-et (Objective-C), a GCD-t és az OperationQueue-t. A Android Thread Documentation szerint egy natív szál létrehozásához az operációs rendszernek ~1 MB-ot kell elkülönítenie a verem számára.

Lényeg

  • Thread — a CPU-ütemezés minimális egysége: minden szál független és saját verme van
  • Szál létrehozása ~1 MB vermet igényel Androidon és 512 KB-ot iOS-en, ezért a poolok hatékonyabbak a közvetlen létrehozásnál
  • Android: Thread, Executors, HandlerThread, Coroutines — a szálabsztrakció négy szintje
  • iOS: Thread (alacsony szintű), GCD (DispatchQueue), OperationQueue (magas szintű)
  • Thread safety — a mutable adatokhoz való megosztott hozzáférés szinkronizálást igényel: locks, atomic, serial queues

Mi az a Thread

Thread (végrehajtási szál) — független utasítássorozat, amelyet az operációs rendszer a CPU magjára ütemezhet. Minden folyamat (alkalmazás) legalább egy szálat tartalmaz — a Main Thread-et. A további szálak a feladatok párhuzamos végrehajtására jönnek létre. Minden szálnak saját programverme (lokális változókkal), programszámlálója (PC) és regiszterei vannak. A heap memória közös a folyamat összes szálában.

A mobil operációs rendszerekben a szálakat preemptív multitaskinggal ütemezik: az operációs rendszer bármikor megszakíthatja egy szál végrehajtását, és átadhatja a vezérlést egy másiknak (context switch). A kontextusváltás költséges művelet (1–10 mikroszekundum), mert a CPU regiszterek mentését/visszaállítását, a TLB frissítését és a gyorsítótárak kiürítését igényli. Éppen ezért a túlzott számú szál (több száz és több ezer) rontja a teljesítményt — az operációs rendszer több időt tölt váltással, mint végrehajtással.

Szál és folyamat különböző fogalmak. A folyamat az alkalmazás egy példánya elkülönített virtuális memóriával. A folyamaton belüli szál ezt a memóriát megosztja a többi szállal. Androidon az alkalmazás minden komponense (Activity, Service, BroadcastReceiver) egy folyamatban működik, de különböző szálakon futhat. Az iOS-alkalmazás szintén egyetlen folyamat, amely lehetőséget ad további szálak létrehozására a GCD vagy a Thread révén.

A szál életciklusa: állapotok és átmenetek

Minden szál a Java/Kotlin (Android) és az NSThread (iOS) esetében öt állapoton megy keresztül: New (létrehozva), Runnable (végrehajtásra kész), Running (a CPU-n fut), Blocked/Waiting (erőforrásra vagy értesítésre vár), Terminated (befejezve). Az állapotok közötti átmeneteket az operációs rendszer ütemezője és a szinkronizálási primitívek irányítják. A fejlesztő befolyásolhatja a szál prioritását (Thread.setPriority()) és állapotát (sleep, join, interrupt).

Androidon a szál Blocked állapotba kerül, amikor foglalt monitort próbál megszerezni (synchronized), Object.wait() vagy Thread.sleep() hívásakor. iOS-en — NSCondition.wait(), pthread_cond_wait() vagy dispatch_semaphore_wait() hívásakor. Blocked állapotban a szál nem fogyaszt CPU-t, de memóriát foglal (verem). A szál másik szálból megszakítható (interrupted), InterruptedException (Java) fogadásával vagy isCancelled (Kotlin Coroutines) ellenőrzésével.

Állapot Leírás Átmenet módja
New A szál létrehozva, de nem indítva Thread() konstruktor
Runnable A szál végrehajtásra kész, CPU-ra vár thread.start()
Running A szál a CPU magján fut Az operációs rendszer ütemezője
Blocked/Waiting A szál erőforrásra, monitorra vagy értesítésre vár synchronized, wait(), sleep()
Terminated A szál befejezte a run()-t, vagy megszakították run() befejezve, interrupt()

Context Switch és költsége

Context switch (kontextusváltás) — az a művelet, amelynek során az operációs rendszer elmenti az aktuális szál állapotát (regiszterek, PC, TLB), és betölti egy másik elmentett állapotát. A mobil rendszerekben (Linux + ART, iOS-hez XNU) a context switch 1–10 mikroszekundumig tart. Ha egy szál 100 mikroszekundum alatt hajt végre egy feladatot, a context switch pedig 5-ig tart, akkor az idő 5%-a vész el. A context switch minimalizálásához az iOS work stealing-el használja a GCD-t, az Android pedig fixedThreadCount-poolokat.

Thread Androidon: a Thread-től a Coroutines-ig

Az Android az alacsony szintű java.lang.Thread-től a modern korutinokig fejlődött. Az absztrakció minden szintje több lehetőséget ad kisebb többletköltséggel. A Thread az alapsztály, de közvetlen létrehozása nem ajánlott: az új szálat nem kezeli pool, nehéz nyomon követni és megszakítani. Az AsyncTask (API 30-tól deprecated) előrelépés volt, de memóriavesztéstől és a konfigurációk kényelmetlen kezelésétől szenvedett.

A HandlerThread — a Thread speciális alosztálya Looper-rel, amely üzenetsort tud feldolgozni. Háttérszálon futó feladatok szekvenciális végrehajtására használják, például adatok Room-ba vagy fájlokba írására. A HandlerThread a start() hívással jön létre, majd a Handler(handlerThread.looper) révén üzeneteket és Runnable-t lehet küldeni. A handlerThread.quit() hívás leállítja a Looper-t és befejezi a szálat.

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

class ThreadExample {

    // 1. A Thread közvetlen létrehozása (nem ajánlott)
    fun directThread() {
        val thread = Thread(Runnable {
            Thread.sleep(1000)
            print("Közvetlen szál végrehajtva")
        })
        thread.start()
    }

    // 2. HandlerThread szekvenciális háttérfeladatokhoz
    fun handlerThreadExample() {
        val handlerThread = HandlerThread("BackgroundQueue")
        handlerThread.start()

        val handler = Handler(handlerThread.looper)
        handler.post {
            // Szekvenciális végrehajtás háttérszálon
            Thread.sleep(500)
            print("HandlerThread: a feladat végrehajtva")
        }

        // Szál leállítása (akkor fut le, amikor a feladatok befejeződnek)
        handlerThread.quitSafely()
    }

    // 3. Executors — szálpool
    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 szabvány
    suspend fun coroutineExample() = kotlinx.coroutines.withContext(
        kotlinx.coroutines.Dispatchers.Default
    ) {
        print("Coroutine on thread: ${Thread.currentThread().getName()}")
    }
}

A ThreadExample példa mind a négy szálabsztrakciós szintet megmutatja Androidon. A Thread közvetlen létrehozása a legalacsonyabb szintű és leghatékonyatlanabb megközelítés. A HandlerThread hasznos a háttérben futó szekvenciális feladatokhoz. Az Executors.newFixedThreadPool(4) 4 szálból álló poolt hoz létre legfeljebb 10 feladat párhuzamos végrehajtásához. A Kotlin Coroutines a Dispatchers.Default-tal — modern, hatékony és biztonságos módszer.

HandlerThread: szekvenciális háttérfeladatok

HandlerThread — a Thread specializált alosztálya beépített Looper-rel és üzenetsorral. A start() hívással jön létre, majd a Handler(handlerThread.looper) révén Runnable és üzenetek küldhetők. A HandlerThread a feladatokat szigorúan szekvenciálisan hajtja végre — a következő feladat nem kezdődik el az előző befejezéséig. Ez kényelmes a Room-ba vagy fájlokba íráskor, ahol a műveletek sorrendje kritikus. A quitSafely() hívás az aktuális feladat befejezése után állítja le a Looper-t.

Thread iOS-en: Thread, GCD és OperationQueue

Az iOS is három szintet kínál a szálak kezelésére. Thread (Swiftben Thread, Objective-C-ben NSThread) — alacsony szintű API, amely közvetlenül hoz létre natív szálat. GCD (Grand Central Dispatch) a DispatchQueue-n keresztül — az iOS-fejlesztők fő eszköze, amely automatikusan kezeli a szálpoolt. OperationQueue — magas szintű absztrakció a GCD felett, függőségek, prioritások és megszakítás támogatásával.

A Thread közvetlen használata a modern iOS-fejlesztésben rendkívül ritka — a GCD minden szükséges lehetőséget biztosít automatikus memória- és szálkezeléssel. A Thread csak speciális esetekben használatos: thread-local storage (threadDictionary) beállítása, RunLoop létrehozása háttérszálhoz, vagy pthread_t-t váró C könyvtárakkal való integráció.

swift
import Foundation

class ThreadManager {

    // 1. Thread (alacsony szintű)
    func createThread() {
        let thread = Thread {
            // A kód új szálon fut
            print("Current thread: \(Thread.current)")
        }
        thread.name = "com.app.worker"
        thread.qualityOfService = .utility
        thread.start()
    }

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

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

        // Barrier az írás szinkronizálásához
        queue.async(flags: .barrier) {
            // Exkluzív hozzáférés írás közben
            print("Barrier write: exkluzív hozzáférés")
        }
    }

    // 3. OperationQueue függőségekkel
    func operationQueueExample() {
        let queue = OperationQueue()
        queue.maxConcurrentOperationCount = 2
        queue.qualityOfService = .background

        let download = BlockOperation {
            print("Letöltés...")
        }
        let process = BlockOperation {
            print("Feldolgozás...")
        }
        let save = BlockOperation {
            print("Mentés...")
        }

        // Függőségek: download -> process -> save
        process.addDependency(download)
        save.addDependency(process)

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

// Thread-safe gyűjtemény GCD barrieren keresztül
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)
        }
    }
}

A ThreadSafeArray osztály a Concurrent Read / Exclusive Write mintát mutatja be GCD barrieren keresztül. Az olvasás a queue.sync{} révén párhuzamosan, több szálból történik. Az írás a queue.async(flags: .barrier) révén minden más műveletet (olvasást és írást egyaránt) blokkol az írás befejezéséig. Ez hatékonyabb, mint a synchronized blokkok, mert nem blokkolja az olvasókat, amíg nincs írás.

iOS Thread vs GCD: mikor használjuk közvetlenül a Thread-et

A Thread közvetlen használata iOS-en három esetben indokolt: thread-local storage (Thread.current.threadDictionary) — a szálhoz kötött adatok tárolása; speciális RunLoop létrehozása háttérszálon performSelector:onThread: paranccsal; a pthread_t-t váró C/C++ könyvtárakkal való integráció. Minden más esetben a GCD a DispatchQueue-n keresztül előnyösebb — automatikusan kezeli a szálpoolt és az energiafogyasztást.

Szálszinkronizálás: locks, atomic, serial queues

Race condition (versenyhelyzet) akkor alakul ki, ha két vagy több szál egyszerre fér hozzá megosztott adatokhoz, és legalább az egyik írást végez. Az eredmény a végrehajtás sorrendjétől (timing) függ, és kiszámíthatatlan. A race condition megelőzéséhez szinkronizálási primitíveket használnak. A mobilfejlesztésben elérhetők zárak (synchronized, NSLock), atomi műveletek (AtomicInteger, iOS atomic tulajdonságok) és sorok (serial queue).

A primitív kiválasztása a forgatókönyvtől függ. Egyszerű számlálókhoz és zászlókhoz az atomi műveletek (AtomicInteger, atomic property) elegendők. Több műveletet tartalmazó kritikus szakaszokhoz — zárak (synchronized, NSLock). Összetett adatszerkezetekhez — serial DispatchQueue vagy GCD barrier. A zárakat könnyebb megérteni, de hajlamosak a deadlockra és livelockra. A sorok bonyolultabbak, de biztonságosabbak.

kotlin
// Szinkronizálás Android/Kotlin környezetben
import java.util.concurrent.atomic.AtomicInteger
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

class Counter {

    // 1. AtomicInteger — egyszerű számlálókhoz
    private val atomicCount = AtomicInteger(0)
    fun incrementAtomic() = atomicCount.incrementAndGet()

    // 2. synchronized — kritikus szakaszokhoz
    @Synchronized
    fun synchronizedOperation() {
        // Egyszerre csak egy szál
        doWork()
    }

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

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

// Deadlock-példa: A blokkolja B-t, B blokkolja A-t
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") }
    }
}

A Counter három szinkronizálási megközelítést mutat be. AtomicInteger.incrementAndGet() — zár nélküli atomi művelet (CAS). @Synchronized — a Java beépített monitorja, a teljes objektumot blokkolja. Mutex.withLock — korutinos mutex, a szál blokkolása helyett a korutint függeszti fel (hatékonyabb). A DeadlockExample klasszikus deadlockot mutat: két szál különböző sorrendben szerzi meg a zárakat.

Thread Pools: miért jobb az Executors, mint a Thread

Thread Pool (szálpool) — előre létrehozott szálak halmaza, amelyeket a feladatok végrehajtására újrahasznosítanak. Ahelyett, hogy minden feladathoz új szálat hoznának létre (költséges), a pool szabad szálat vesz a poolból. Ha nincs szabad szál, a feladat sorba kerül. A pool automatikusan kezeli a méretet: csúcs terhelésnél új szálak jönnek létre, az üresjáratban lévő szálak befejeződnek. Ez tízszeresére csökkenti a szállétrehozás többletköltségét.

Androidon az Executors.newFixedThreadPool(4) 4 szálból álló poolt hoz létre. Ha egyszerre 10 feladat érkezik, 4 azonnal elindul, 6 pedig a sorban várakozik. Az Executors.newCachedThreadPool() szükség szerint (korlát nélkül) hoz létre szálakat, és 60 másodperc után befejezi az üresjáratban lévőket. Az iOS-hez a GCD automatikusan biztosítja a globális sorok pooljait, amelyek mérete a CPU-magok számához és az aktuális terheléshez igazodik.

A Kotlin Coroutines esetében a szálpoolok a diszpécserek belsejében vannak elrejtve. A Dispatchers.Default a CPU-magok számával megegyező méretű poolt használ (minimum 2). A Dispatchers.IO — 64 szál (elég több száz IO-bound feladathoz, mert a többség CPU-foglalás nélkül vár a be- és kimenetre). Minden diszpécser automatikusan méretezi a poolt a terheléshez, üresjáratban energiát takarítva meg.

Gyakran ismételt kérdések

Mi az a Thread a mobilfejlesztésben?

Thread — a kód végrehajtásának alapegysége egy alkalmazásban. Minden folyamatnak sok szálja lehet, amelyek megosztják a memóriát, de saját vermük van. A mobilfejlesztésben a szálakat a feladatok párhuzamos végrehajtására használják a UI blokkolása nélkül. Az Android Thread-et, Executors-t, HandlerThread-et és Coroutines-t használ. Az iOS Thread-et, GCD-t (DispatchQueue) és OperationQueue-t használ.

Miért nem ajánlott közvetlenül létrehozni a Thread-et?

Thread létrehozása ~1 MB verem elkülönítését igényli Androidon és ~512 KB-ot iOS-en — ez költséges művelet. 1000 feladat esetén 1000 szál közvetlen létrehozása ~1 GB-ot igényel csak a vermekre, plusz a context switch többletköltségét. A Thread helyett használjon poolokat (Executors, GCD) vagy korutinokat — ezek újrahasznosítják a szálakat, tízszeresére csökkentve a többletköltséget.

Mi az a race condition, és hogyan kerülhető el?

Race condition — kiszámíthatatlan viselkedés, amikor több szál egyszerre fér hozzá megosztott adatokhoz írással. Három módon kerülhető el: atomi típusok (AtomicInteger), zárak (synchronized, NSLock) használatával, vagy a hozzáférés soron keresztüli szerializálásával (serial DispatchQueue, Actor Kotlinban). A legjobb gyakorlat — a megosztott mutable állapot minimalizálása és az immutability használata.

Miben különbözik a Thread a korutintól?

Thread — natív rendszerobjektum, amely ~1 MB vermet foglal, és az operációs rendszer magjához kötött. Korutin — a Kotlin könnyű végrehajtási egysége, amely nem kötődik konkrét szálhoz, és blokkolás nélkül felfüggeszthető (suspend). Egyetlen szál több ezer korutint hajthat végre. A korutinok memóriahatékonyabbak, és lehetővé teszik aszinkron kód írását callbacks nélkül.

Hogyan lehet elkapni a deadlockot egy mobilalkalmazásban?

Deadlock az alkalmazás teljes lefagyásaként nyilvánul meg ANR nélkül. Androidon a Thread.getAllStackTraces() segítségével dumpolja az összes szál vermét — két szál egymás zárjaira fog várni. iOS-en — Thread.callStackSymbols. Eszközök: Android Studio Profiler (Threads fül), Instruments (iOS, Thread State View). Megelőzés: rögzített sorrendben vegye meg a zárakat, használjon tryLock-ot időtúllépéssel.

Összegzés

  • Thread — a CPU minimális egysége: független végrehajtás saját veremmel, a heap memória közös
  • Öt állapot: New, Runnable, Running, Blocked/Waiting, Terminated
  • Android a Thread → AsyncTask → Executors → HandlerThread → Coroutines úton fejlődött
  • iOS Thread-et, GCD-t (DispatchQueue) és OperationQueue-t kínál — alacsonytól a magas szintig
  • Race condition zárakkal (synchronized, NSLock), atomi típusokkal és serial-sorokkal oldható meg
  • Deadlock a zárak keresztirányú megszerzésekor alakul ki — rögzített sorrenddel előzhető meg
  • Thread Pool hatékonyabb, mint új Thread-ek létrehozása: újrahasznosítja a szálakat, csökkenti a context switch többletköltségét

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